Module Name:    src
Committed By:   martin
Date:           Fri Aug 29 11:42:15 UTC 2014

Modified Files:
        src/distrib/sets/lists/man [netbsd-7]: mi
        src/share/man/man4 [netbsd-7]: Makefile
        src/sys/arch/hp300/conf [netbsd-7]: GENERIC files.hp300 majors.hp300
        src/sys/arch/hp300/hp300 [netbsd-7]: intr.c locore.s
        src/sys/conf [netbsd-7]: files
Added Files:
        src/share/man/man4 [netbsd-7]: arcofi.4
        src/sys/arch/hp300/dev [netbsd-7]: arcofi_dio.c
        src/sys/dev/ic [netbsd-7]: arcofi.c arcofivar.h

Log Message:
Pull up following revision(s) (requested by tsutsui in ticket #62):
        sys/arch/hp300/conf/files.hp300: revision 1.89
        share/man/man4/arcofi.4: revision 1.1
        share/man/man4/arcofi.4: revision 1.2
        share/man/man4/Makefile: revision 1.617
        sys/arch/hp300/hp300/intr.c: revision 1.41
        sys/conf/files: revision 1.1100
        sys/arch/hp300/hp300/locore.s: revision 1.171
        distrib/sets/lists/man/mi: revision 1.1486
        sys/dev/ic/arcofivar.h: revision 1.1
        sys/arch/hp300/conf/majors.hp300: revision 1.26
        sys/arch/hp300/dev/arcofi_dio.c: revision 1.1
        sys/arch/hp300/conf/GENERIC: revision 1.188
        sys/dev/ic/arcofi.c: revision 1.1
Add new arcofi(4) audio driver for NetBSD/hp300, ported from OpenBSD.
The arcofi(4) is a driver for the HP "Audio1" device
(Siemens PSB 2160 "ARCOFI" phone quality audio chip)
found on the HP9000/425e and HP9000/{705,710,745,747} models
(but only hp300 attachment is ported for now).
The chip supports 8-bit mono 8kHz U-law, A-law and
16-bit mono slinear_be formats.
The old HP9000/425e playing tunes with this new arcofi(4) audio driver
was also demonstrated at Open Source Conference 2014 Shimane.
Add a man page for arcofi(4) driver.  From OpenBSD.
Fix date.


To generate a diff of this commit:
cvs rdiff -u -r1.1485 -r1.1485.2.1 src/distrib/sets/lists/man/mi
cvs rdiff -u -r1.616 -r1.616.2.1 src/share/man/man4/Makefile
cvs rdiff -u -r0 -r1.2.2.2 src/share/man/man4/arcofi.4
cvs rdiff -u -r1.184 -r1.184.2.1 src/sys/arch/hp300/conf/GENERIC
cvs rdiff -u -r1.88 -r1.88.2.1 src/sys/arch/hp300/conf/files.hp300
cvs rdiff -u -r1.25 -r1.25.28.1 src/sys/arch/hp300/conf/majors.hp300
cvs rdiff -u -r0 -r1.1.2.2 src/sys/arch/hp300/dev/arcofi_dio.c
cvs rdiff -u -r1.40 -r1.40.34.1 src/sys/arch/hp300/hp300/intr.c
cvs rdiff -u -r1.170 -r1.170.4.1 src/sys/arch/hp300/hp300/locore.s
cvs rdiff -u -r1.1096 -r1.1096.2.1 src/sys/conf/files
cvs rdiff -u -r0 -r1.1.2.2 src/sys/dev/ic/arcofi.c src/sys/dev/ic/arcofivar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/man/mi
diff -u src/distrib/sets/lists/man/mi:1.1485 src/distrib/sets/lists/man/mi:1.1485.2.1
--- src/distrib/sets/lists/man/mi:1.1485	Sat Aug  9 11:33:53 2014
+++ src/distrib/sets/lists/man/mi	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1485 2014/08/09 11:33:53 apb Exp $
+# $NetBSD: mi,v 1.1485.2.1 2014/08/29 11:42:15 martin Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -826,6 +826,7 @@
 ./usr/share/man/cat4/aps.0			man-sys-catman		.cat
 ./usr/share/man/cat4/arc/intro.0		man-sys-catman		.cat
 ./usr/share/man/cat4/arcmsr.0			man-sys-catman		.cat
+./usr/share/man/cat4/arcofi.0			man-sys-catman		.cat
 ./usr/share/man/cat4/aria.0			man-sys-catman		.cat
 ./usr/share/man/cat4/arm26/arckbd.0		man-obsolete		obsolete
 ./usr/share/man/cat4/arm26/arcwskbd.0		man-obsolete		obsolete
@@ -3911,6 +3912,7 @@
 ./usr/share/man/html4/aps.html			man-sys-htmlman		html
 ./usr/share/man/html4/arc/intro.html		man-sys-htmlman		html
 ./usr/share/man/html4/arcmsr.html		man-sys-htmlman		html
+./usr/share/man/html4/arcofi.html		man-sys-htmlman		html
 ./usr/share/man/html4/aria.html			man-sys-htmlman		html
 ./usr/share/man/html4/arp.html			man-sys-htmlman		html
 ./usr/share/man/html4/artsata.html		man-sys-htmlman		html
@@ -6691,6 +6693,7 @@
 ./usr/share/man/man4/aps.4			man-sys-man		.man
 ./usr/share/man/man4/arc/intro.4		man-sys-man		.man
 ./usr/share/man/man4/arcmsr.4			man-sys-man		.man
+./usr/share/man/man4/arcofi.4			man-sys-man		.man
 ./usr/share/man/man4/aria.4			man-sys-man		.man
 ./usr/share/man/man4/arm26/arckbd.4		man-obsolete		obsolete
 ./usr/share/man/man4/arm26/arcwskbd.4		man-obsolete		obsolete

Index: src/share/man/man4/Makefile
diff -u src/share/man/man4/Makefile:1.616 src/share/man/man4/Makefile:1.616.2.1
--- src/share/man/man4/Makefile:1.616	Thu Jul 24 21:08:50 2014
+++ src/share/man/man4/Makefile	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.616 2014/07/24 21:08:50 alnsn Exp $
+#	$NetBSD: Makefile,v 1.616.2.1 2014/08/29 11:42:15 martin Exp $
 #	@(#)Makefile	8.1 (Berkeley) 6/18/93
 
 MAN=	aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
@@ -7,7 +7,7 @@ MAN=	aac.4 ac97.4 acardide.4 aceride.4 a
 	ahcisata.4 ahd.4 \
 	aibs.4 alc.4 ale.4 alipm.4 altmem.4 altq.4 amdpm.4 amdtemp.4 amhphy.4 \
 	amr.4 aps.4 asus.4 \
-	an.4 arcmsr.4 aria.4 artsata.4 ata.4 atalk.4 ataraid.4 \
+	an.4 arcmsr.4 arcofi.4 aria.4 artsata.4 ata.4 atalk.4 ataraid.4 \
 	ath.4 athn.4 atphy.4 atppc.4 attimer.4 atw.4 \
 	auacer.4 audio.4 audiocs.4 auich.4 \
 	auixp.4 autri.4 auvia.4 awi.4 azalia.4 \

Index: src/sys/arch/hp300/conf/GENERIC
diff -u src/sys/arch/hp300/conf/GENERIC:1.184 src/sys/arch/hp300/conf/GENERIC:1.184.2.1
--- src/sys/arch/hp300/conf/GENERIC:1.184	Fri Jul 18 17:59:12 2014
+++ src/sys/arch/hp300/conf/GENERIC	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-# $NetBSD: GENERIC,v 1.184 2014/07/18 17:59:12 tsutsui Exp $
+# $NetBSD: GENERIC,v 1.184.2.1 2014/08/29 11:42:15 martin Exp $
 #
 # GENERIC machine description file
 #
@@ -22,7 +22,7 @@ include 	"arch/hp300/conf/std.hp300"
 
 options 	INCLUDE_CONFIG_FILE	# embed config file in kernel binary
 
-#ident 		"GENERIC-$Revision: 1.184 $"
+#ident 		"GENERIC-$Revision: 1.184.2.1 $"
 
 makeoptions	COPTS="-O2 -fno-reorder-blocks"	# see share/mk/sys.mk
 
@@ -279,6 +279,10 @@ ch*		at scsibus? target ? lun ?	# SCSI c
 ss*		at scsibus? target ? lun ?	# SCSI scanners
 uk*		at scsibus? target ? lun ?	# unknown SCSI devices
 
+# 425e digital audio
+arcofi* 	at dio? scode ?
+audio*		at arcofi?
+
 #
 # Pseudo-devices
 #

Index: src/sys/arch/hp300/conf/files.hp300
diff -u src/sys/arch/hp300/conf/files.hp300:1.88 src/sys/arch/hp300/conf/files.hp300:1.88.2.1
--- src/sys/arch/hp300/conf/files.hp300:1.88	Sun Apr 20 04:12:54 2014
+++ src/sys/arch/hp300/conf/files.hp300	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-#	$NetBSD: files.hp300,v 1.88 2014/04/20 04:12:54 tsutsui Exp $
+#	$NetBSD: files.hp300,v 1.88.2.1 2014/08/29 11:42:15 martin Exp $
 #
 # hp300-specific configuration info
 
@@ -147,6 +147,10 @@ file	arch/hp300/dev/dcm.c		dcm needs-fla
 attach	le at dio: le24
 file	arch/hp300/dev/if_le.c		le
 
+# 425e digital audio
+attach	arcofi at dio with arcofi_dio
+file	arch/hp300/dev/arcofi_dio.c	arcofi_dio
+
 # HP-IB interfaces
 define	hpibdev { }
 

Index: src/sys/arch/hp300/conf/majors.hp300
diff -u src/sys/arch/hp300/conf/majors.hp300:1.25 src/sys/arch/hp300/conf/majors.hp300:1.25.28.1
--- src/sys/arch/hp300/conf/majors.hp300:1.25	Thu Jun 30 20:09:30 2011
+++ src/sys/arch/hp300/conf/majors.hp300	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-#	$NetBSD: majors.hp300,v 1.25 2011/06/30 20:09:30 wiz Exp $
+#	$NetBSD: majors.hp300,v 1.25.28.1 2014/08/29 11:42:15 martin Exp $
 #
 # Device majors for hp300
 #
@@ -48,6 +48,7 @@ device-major	ch		char 48			ch
 device-major	ss		char 49			ss
 device-major	uk		char 50			uk
 device-major	ses		char 51			ses
+device-major	audio		char 52			audio
 
 device-major	nsmb		char 98			nsmb
 

Index: src/sys/arch/hp300/hp300/intr.c
diff -u src/sys/arch/hp300/hp300/intr.c:1.40 src/sys/arch/hp300/hp300/intr.c:1.40.34.1
--- src/sys/arch/hp300/hp300/intr.c:1.40	Mon Dec 20 00:25:33 2010
+++ src/sys/arch/hp300/hp300/intr.c	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-/*	$NetBSD: intr.c,v 1.40 2010/12/20 00:25:33 matt Exp $	*/
+/*	$NetBSD: intr.c,v 1.40.34.1 2014/08/29 11:42:15 martin Exp $	*/
 
 /*-
  * Copyright (c) 1996, 1997, 1999 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.40 2010/12/20 00:25:33 matt Exp $");
+__KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.40.34.1 2014/08/29 11:42:15 martin Exp $");
 
 #define _HP300_INTR_H_PRIVATE
 
@@ -45,6 +45,8 @@ __KERNEL_RCSID(0, "$NetBSD: intr.c,v 1.4
 #include <sys/cpu.h>
 #include <sys/intr.h>
 
+#include "audio.h"
+
 /*
  * The location and size of the autovectored interrupt portion
  * of the vector table.
@@ -209,6 +211,12 @@ intr_dispatch(int evec /* format | vecto
 	    ih = LIST_NEXT(ih, ih_q))
 		handled |= (*ih->ih_fn)(ih->ih_arg);
 
+#if NAUDIO > 0
+	/* hardclock() on ipl 6 is already handled in locore.s */
+	if (ipl == 6)
+		return;
+#endif
+
 	if (handled)
 		straycount = 0;
 	else if (++straycount > 50)

Index: src/sys/arch/hp300/hp300/locore.s
diff -u src/sys/arch/hp300/hp300/locore.s:1.170 src/sys/arch/hp300/hp300/locore.s:1.170.4.1
--- src/sys/arch/hp300/hp300/locore.s:1.170	Sat Mar 15 09:26:36 2014
+++ src/sys/arch/hp300/hp300/locore.s	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-/*	$NetBSD: locore.s,v 1.170 2014/03/15 09:26:36 tsutsui Exp $	*/
+/*	$NetBSD: locore.s,v 1.170.4.1 2014/08/29 11:42:15 martin Exp $	*/
 
 /*
  * Copyright (c) 1980, 1990, 1993
@@ -97,6 +97,7 @@
 #include <hp300/hp300/leds.h>
 #endif
 
+#include "audio.h"
 #include "ksyms.h"
 
 #define MMUADDR(ar)	movl	_C_LABEL(MMUbase),ar
@@ -951,6 +952,12 @@ Lrecheck:
 	CLKADDR(%a0)
 	movb	%a0@(CLKSR),%d0		| see if anything happened
 	jmi	Lclkagain		|  while we were in hardclock/statintr
+#if NAUDIO >0
+	movw	%sp@(22),%sp@-		| push exception vector info
+	clrw	%sp@-
+	jbsr	_C_LABEL(intr_dispatch)	| call dispatch routine
+	addql	#4,%sp
+#endif
 	INTERRUPT_RESTOREREG
 	subql	#1,_C_LABEL(idepth)	| exiting from interrupt
 	jra	_ASM_LABEL(rei)		| all done

Index: src/sys/conf/files
diff -u src/sys/conf/files:1.1096 src/sys/conf/files:1.1096.2.1
--- src/sys/conf/files:1.1096	Sun Aug 10 16:44:35 2014
+++ src/sys/conf/files	Fri Aug 29 11:42:15 2014
@@ -1,4 +1,4 @@
-#	$NetBSD: files,v 1.1096 2014/08/10 16:44:35 tls Exp $
+#	$NetBSD: files,v 1.1096.2.1 2014/08/29 11:42:15 martin Exp $
 #	@(#)files.newconf	7.5 (Berkeley) 5/10/93
 
 version 	20100430
@@ -911,6 +911,10 @@ file	dev/ic/ad1848.c			ad1848
 define	am7930
 file	dev/ic/am7930.c			am7930
 
+# Siemens PSB2160 audio codec, as found in HP systems
+device	arcofi: audiobus, auconv, mulaw
+file	dev/ic/arcofi.c			arcofi
+
 # SPARC `SUNW,audiocs'
 #
 device	audiocs: audiobus, auconv, ad1848

Added files:

Index: src/share/man/man4/arcofi.4
diff -u /dev/null src/share/man/man4/arcofi.4:1.2.2.2
--- /dev/null	Fri Aug 29 11:42:15 2014
+++ src/share/man/man4/arcofi.4	Fri Aug 29 11:42:15 2014
@@ -0,0 +1,102 @@
+.\"	$NetBSD: arcofi.4,v 1.2.2.2 2014/08/29 11:42:15 martin Exp $
+.\"	$OpenBSD: arcofi.4,v 1.4 2011/12/27 10:28:35 jmc Exp $
+.\"
+.\"
+.\" Copyright (c) 2011 Miodrag Vallat.
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd August 25, 2014
+.Dt ARCOFI 4
+.Os
+.Sh NAME
+.Nm arcofi
+.Nd Siemens PSB2160 audio codec
+.Sh SYNOPSIS
+.Cd "arcofi* at dio?"
+.\" .Cd "arcofi* at gsc?"
+.Cd "audio*  at audiobus?"
+.Sh DESCRIPTION
+The
+.Nm
+driver supports the HP
+.Dq Audio1
+audio devices, based upon the Siemens PSB2160
+.Dq ARCOFI
+codec, to implement the audio device interface described in
+.Xr audio 4 .
+.Pp
+This device is found onboard HP 9000 workstations models 425e, 705 and 710.
+.Pp
+The
+.Nm
+is limited to a phone-quality mono, 8000 Hz sound.
+.Ss AUDIOCTL SETTINGS
+The following encodings are supported:
+.Pp
+.Bl -tag -width AUDIO_ENCODING_SLINEAR_BE -offset indent -compact
+.It Li AUDIO_ENCODING_ULAW
+.It Li AUDIO_ENCODING_ALAW
+.It Li AUDIO_ENCODING_SLINEAR_BE
+Natively supported.
+.Pp
+.It Li AUDIO_ENCODING_SLINEAR
+.It Li AUDIO_ENCODING_SLINEAR_LE
+.It Li AUDIO_ENCODING_ULINEAR_LE
+.It Li AUDIO_ENCODING_ULINEAR_BE
+Software converted to
+.Li AUDIO_ENCODING_SLINEAR_BE
+encoding.
+.El
+.Ss MIXERCTL SETTINGS
+The
+.Nm
+has three audio ports:
+.Pp
+.Bl -tag -width "outputs.lineXXX" -offset indent -compact
+.It Cm inputs.line
+The
+.Sq line in
+jack connector.
+.It Cm outputs.line
+The
+.Sq line out
+jack connector.
+.It Cm outputs.speaker
+The built-in speaker.
+.El
+.Pp
+Each port has a volume control, and can be muted.
+.Pp
+The
+.Cm outputs.line
+and
+.Cm outputs.speaker
+volume settings are tied to the same hardware setting.
+.Sh SEE ALSO
+.Xr audioctl 1 ,
+.Xr mixerctl 1 ,
+.Xr ioctl 2 ,
+.Xr audio 4 ,
+.Xr dio 4 ,
+.\" .Xr gsc 4 ,
+.Xr intro 4
+.Sh HISTORY
+The
+.Nm
+driver was written for
+.Ox
+and first appeared in
+.Ox 5.1 ,
+and was ported to
+.Nx .

Index: src/sys/arch/hp300/dev/arcofi_dio.c
diff -u /dev/null src/sys/arch/hp300/dev/arcofi_dio.c:1.1.2.2
--- /dev/null	Fri Aug 29 11:42:15 2014
+++ src/sys/arch/hp300/dev/arcofi_dio.c	Fri Aug 29 11:42:15 2014
@@ -0,0 +1,94 @@
+/*	$NetBSD: arcofi_dio.c,v 1.1.2.2 2014/08/29 11:42:15 martin Exp $	*/
+/*	$OpenBSD: arcofi_dio.c,v 1.1 2011/12/21 23:12:03 miod Exp $	*/
+
+/*
+ * Copyright (c) 2011 Miodrag Vallat.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/device.h>
+#include <sys/bus.h>
+#include <sys/intr.h>
+
+#include <sys/audioio.h>
+#include <dev/audio_if.h>
+#include <dev/ic/arcofivar.h>
+
+#include <hp300/dev/dioreg.h>
+#include <hp300/dev/diovar.h>
+
+#include <hp300/dev/diodevs.h>
+
+#define SOFTINT_AUDIO	SOFTINT_SERIAL	/* XXX */
+
+static void	arcofi_dio_attach(device_t, device_t, void *);
+static int	arcofi_dio_match(device_t, cfdata_t, void *);
+
+struct arcofi_dio_softc {
+	struct arcofi_softc	sc_arcofi;
+
+	struct bus_space_tag sc_tag;
+};
+
+CFATTACH_DECL_NEW(arcofi_dio, sizeof(struct arcofi_dio_softc),
+    arcofi_dio_match, arcofi_dio_attach, NULL, NULL);
+
+static int
+arcofi_dio_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct dio_attach_args *da = aux;
+
+	if (da->da_id != DIO_DEVICE_ID_AUDIO)
+		return 0;
+
+	return 1;
+}
+
+static void
+arcofi_dio_attach(device_t parent, device_t self, void *aux)
+{
+	struct arcofi_dio_softc *adsc = device_private(self);
+	struct arcofi_softc *sc = &adsc->sc_arcofi;
+	struct dio_attach_args *da = aux;
+	bus_space_tag_t iot = &adsc->sc_tag;
+	int ipl;
+
+	sc->sc_dev = self;
+
+	/* XXX is it better to use sc->sc_reg[] to handle odd sparse map? */
+	memcpy(iot, da->da_bst, sizeof(struct bus_space_tag));
+	dio_set_bus_space_oddbyte(iot);
+	sc->sc_iot = iot;
+
+	if (bus_space_map(iot, da->da_addr, DIOII_SIZEOFF, 0,
+	    &sc->sc_ioh) != 0) {
+		aprint_error(": can't map registers\n");
+		return;
+	}
+
+	sc->sc_sih = softint_establish(SOFTINT_AUDIO, arcofi_swintr, sc);
+	if (sc->sc_sih == NULL) {
+		aprint_error(": can't register soft interrupt\n");
+		return;
+	}
+	ipl = da->da_ipl;
+	dio_intr_establish(arcofi_hwintr, sc, ipl, IPL_AUDIO);
+
+	aprint_normal("\n");
+
+	arcofi_attach(sc, "dio");
+}

Index: src/sys/dev/ic/arcofi.c
diff -u /dev/null src/sys/dev/ic/arcofi.c:1.1.2.2
--- /dev/null	Fri Aug 29 11:42:16 2014
+++ src/sys/dev/ic/arcofi.c	Fri Aug 29 11:42:15 2014
@@ -0,0 +1,1274 @@
+/*	$NetBSD: arcofi.c,v 1.1.2.2 2014/08/29 11:42:15 martin Exp $	*/
+/*	$OpenBSD: arcofi.c,v 1.6 2013/05/15 08:29:24 ratchov Exp $	*/
+
+/*
+ * Copyright (c) 2011 Miodrag Vallat.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Driver for the HP ``Audio1'' device, which is a FIFO layer around a
+ * Siemens PSB 2160 ``ARCOFI'' phone quality audio chip.
+ *
+ * It is known to exist in two flavours: on-board the HP9000/425e as a DIO
+ * device, an on-board the HP9000/{705,710,745,747} as a GIO device.
+ *
+ * The FIFO logic buffers up to 128 bytes. When using 8 bit samples and
+ * the logic set to interrupt every half FIFO, the device will interrupt
+ * 125 times per second.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/bus.h>
+#include <sys/intr.h>
+
+#include <sys/audioio.h>
+
+#include <dev/audio_if.h>
+#include <dev/auconv.h>
+#include <dev/mulaw.h>
+
+#include <dev/ic/arcofivar.h>
+
+#include "ioconf.h"
+
+#if 0
+#define	ARCOFI_DEBUG
+#endif
+
+/*
+ * Siemens PSB2160 registers
+ */
+
+/* CMDR */
+#define	CMDR_AD		0x80	/* SP1/PS2 address convention */
+#define	CMDR_READ	0x40
+#define	CMDR_WRITE	0x00
+#define	CMDR_PU		0x20	/* Power Up */
+#define	CMDR_RCS	0x10	/* Receive and transmit in CH B2 */
+#define	CMDR_MASK	0x0f
+
+	/* command	     length	data */
+#define	SOP_0	0x00	/*	5	CR4 CR3 CR2 CR1 */
+#define	COP_1	0x01	/*	5	t1_hi t1_lo f1_hi f1_lo */
+#define	COP_2	0x02	/*	3	gr1 gr2 */
+#define	COP_3	0x03	/*	3	t2_hi t2_lo f2_hi f2_lo */
+#define	SOP_4	0x04	/*	2	CR1 */
+#define	SOP_5	0x05	/*	2	CR2 */
+#define	SOP_6	0x06	/*	2	CR3 */
+#define	SOP_7	0x07	/*	2	CR4 */
+#define	COP_8	0x08	/*	3	dtmf_hi dtmf_lo */
+#define	COP_9	0x09	/*	5	gz a3 a2 a1 */
+#define	COP_A	0x0a	/*	9	fx1 to fx8 */
+#define	COP_B	0x0b	/*	3	gx1 gx2 */
+#define	COP_C	0x0c	/*	9	fr1 to fr 8 */
+#define	COP_D	0x0d	/*	5	fr9 fr10 fx9 fx10 */
+#define	COP_E	0x0e	/*	5	t3_hi t3_lo f3_hi f3_lo */
+
+/* CR1 */
+#define	CR1_GR		0x80	/* GR gain loaded from CRAM vs 0dB */
+#define	CR1_GZ		0x40	/* Z gain loaded from CRAM vs -18dB */
+#define	CR1_FX		0x20	/* X filter loaded from CRAM vs 0dB flat */
+#define	CR1_FR		0x10	/* R filter loaded from CRAM vs 0dB flat */
+#define	CR1_GX		0x08	/* GX gain loaded from CRAM vs 0dB */
+#define	CR1_T_MASK	0x07	/* test mode */
+#define	CR1_DLP		0x07	/* digital loopback via PCM registers */
+#define	CR1_DLM		0x06	/* D/A output looped back to A/D input */
+#define	CR1_DLS		0x05	/* digital loopback via converter registers */
+#define	CR1_IDR		0x04	/* data RAM initialization */
+#define	CR1_BYP		0x03	/* bypass analog frontend */
+#define	CR1_ALM		0x02	/* analog loopback via MUX */
+#define	CR1_ALS		0x01	/* analog loopback via converter registers */
+
+/* CR2 */
+#define	CR2_SD		0x80	/* SD pin set to input vs output */
+#define	CR2_SC		0x40	/* SC pin set to input vs output */
+#define	CR2_SB		0x20	/* SB pin set to input vs output */
+#define	CR2_SA		0x10	/* SA pin set to input vs output */
+#define	CR2_ELS		0x08	/* non-input S pins tristate SIP vs sending 0 */
+#define	CR2_AM		0x04	/* only one device on the SLD bus */
+#define	CR2_TR		0x02	/* three party conferencing */
+#define	CR2_EFC		0x01	/* enable feature control */
+
+/* CR3 */
+#define	CR3_MIC_G_MASK	0xe0		/* MIC input analog gain  */
+#define	CR3_MIC_X_INPUT		0xe0	/* MIC disabled, X input 15.1 dB */
+#define	CR3_MIC_G_17		0xc0	/* 17 dB */
+#define	CR3_MIC_G_22		0xa0	/* 22 dB */
+#define	CR3_MIC_G_28		0x80	/* 28 dB */
+#define	CR3_MIC_G_34		0x60	/* 34 dB */
+#define	CR3_MIC_G_40		0x40	/* 40 dB */
+#define	CR3_MIC_G_46		0x20	/* 46 dB */
+#define	CR3_MIC_G_52		0x00	/* 52 dB (reset default) */
+#define	CR3_AFEC_MASK	0x1c
+#define	CR3_AFEC_MUTE		0x18	/* mute: Hout */
+#define	CR3_AFEC_HFS		0x14	/* hands free: FHM, LS out */
+#define	CR3_AFEC_LH3		0x10	/* loud hearing 3: MIC, H out, LS out */
+#define	CR3_AFEC_LH2		0x0c	/* loud hearing 2: MIC, LS out */
+#define	CR3_AFEC_LH1		0x08	/* loud hearing 1: LS out */
+#define	CR3_AFEC_RDY		0x04	/* ready: MIC, H out */
+#define	CR3_AFEC_POR		0x00	/* power on reset: all off */
+#define	CR3_OPMODE_MASK	0x03
+#define	CR3_OPMODE_LINEAR	0x02	/* linear (16 bit) */
+#define	CR3_OPMODE_MIXED	0x01	/* mixed */
+#define	CR3_OPMODE_NORMAL	0x00	/* normal (A/u-Law) */
+
+/* CR4 */
+#define	CR4_DHF		0x80	/* TX digital high frequency enable */
+#define	CR4_DTMF	0x40	/* DTMF generator enable */
+#define	CR4_TG		0x20	/* tone ring enable */
+#define	CR4_BT		0x10	/* beat tone generator enable */
+#define	CR4_TM		0x08	/* incoming voice enable */
+#define	CR4_BM		0x04	/* beat mode (3 tone vs 2 tone) */
+#define	CR4_PM		0x02	/* tone sent to piezo vs loudspeaker */
+#define	CR4_ULAW	0x01	/* u-Law vs A-Law */
+
+
+/*
+ * Glue logic registers
+ * Note the register values here are symbolic, as actual addresses
+ * depend upon the particular bus the device is connected to.
+ */
+
+#define	ARCOFI_ID		0	/* id (r) and reset (w) register */
+
+#define	ARCOFI_CSR		1	/* status and control register */
+#define	CSR_INTR_ENABLE			0x80
+#define	CSR_INTR_REQUEST		0x40	/* unacknowledged interrupt */
+/* 0x20 and 0x10 used in DIO flavours, to provide IPL */
+#define	CSR_WIDTH_16			0x08	/* 16-bit samples */
+#define	CSR_CTRL_FIFO_ENABLE		0x04	/* connect FIFO to CMDR */
+#define	CSR_DATA_FIFO_ENABLE		0x01	/* connect FIFO to DU/DD */
+
+#define	ARCOFI_FIFO_IR		2	/* FIFO interrupt register */
+#define	FIFO_IR_ENABLE(ev)		((ev) << 4)
+#define	FIFO_IR_EVENT(ev)		(ev)
+#define	FIFO_IR_OUT_EMPTY		0x08
+#define	FIFO_IR_CTRL_EMPTY		0x04
+#define	FIFO_IR_OUT_HALF_EMPTY		0x02
+#define	FIFO_IR_IN_HALF_EMPTY		0x01
+
+#define	ARCOFI_FIFO_SR		3	/* FIFO status register (ro) */
+#define	FIFO_SR_CTRL_FULL		0x20
+#define	FIFO_SR_CTRL_EMPTY		0x10
+#define	FIFO_SR_OUT_FULL		0x08
+#define	FIFO_SR_OUT_EMPTY		0x04
+#define	FIFO_SR_IN_FULL			0x02
+#define	FIFO_SR_IN_EMPTY		0x01
+
+#define	ARCOFI_FIFO_DATA	4	/* data FIFO port */
+
+#define	ARCOFI_FIFO_CTRL	5	/* control FIFO port (wo) */
+
+#define	ARCOFI_FIFO_SIZE	128
+
+#ifdef hp300	/* XXX */
+#define	arcofi_read(sc, r) \
+	bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (r))
+#define	arcofi_write(sc, r, v) \
+	bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (r), (v))
+#else
+#define	arcofi_read(sc, r) \
+	bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[(r)])
+#define	arcofi_write(sc, r, v) \
+	bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[(r)], (v))
+#endif
+
+static int	arcofi_cmd(struct arcofi_softc *, uint8_t, const uint8_t *);
+static int	arcofi_cr3_to_portmask(uint, int);
+static int	arcofi_gain_to_mi(uint);
+static uint	arcofi_mi_to_gain(int);
+static uint	arcofi_portmask_to_cr3(int);
+
+static int	arcofi_open(void *, int);
+static void	arcofi_close(void *);
+static int	arcofi_drain(void *);
+static int	arcofi_query_encoding(void *, struct audio_encoding *);
+static int	arcofi_set_params(void *, int, int,
+		    struct audio_params *, struct audio_params *,
+		    stream_filter_list_t *, stream_filter_list_t *);
+static int	arcofi_round_blocksize(void *, int, int,
+		    const audio_params_t *);
+static int	arcofi_commit_settings(void *);
+static int	arcofi_start_output(void *, void *, int, void (*)(void *),
+		    void *);
+static int	arcofi_start_input(void *, void *, int, void (*)(void *),
+		    void *);
+static int	arcofi_halt_output(void *);
+static int	arcofi_halt_input(void *);
+static int	arcofi_getdev(void *, struct audio_device *);
+static int	arcofi_set_port(void *, mixer_ctrl_t *);
+static int	arcofi_get_port(void *, mixer_ctrl_t *);
+static int	arcofi_query_devinfo(void *, mixer_devinfo_t *);
+static int	arcofi_get_props(void *);
+static void	arcofi_get_locks(void *, kmutex_t **, kmutex_t **);
+
+static const struct audio_hw_if arcofi_hw_if = {
+	.open		  = arcofi_open,
+	.close		  = arcofi_close,
+	.drain		  = arcofi_drain,
+	.query_encoding	  = arcofi_query_encoding,
+	.set_params	  = arcofi_set_params,
+	.round_blocksize  = arcofi_round_blocksize,
+	.commit_settings  = arcofi_commit_settings,
+	.start_output	  = arcofi_start_output,
+	.start_input	  = arcofi_start_input,
+	.halt_output	  = arcofi_halt_output,
+	.halt_input	  = arcofi_halt_input,
+	.speaker_ctl	  = NULL,
+	.getdev		  = arcofi_getdev,
+	.setfd		  = NULL,
+	.set_port	  = arcofi_set_port,
+	.get_port	  = arcofi_get_port,
+	.query_devinfo	  = arcofi_query_devinfo,
+	.allocm		  = NULL,
+	.freem		  = NULL,
+	.round_buffersize = NULL,
+	.mappage	  = NULL,
+	.get_props	  = arcofi_get_props,
+	.trigger_output	  = NULL,
+	.trigger_input	  = NULL,
+	.dev_ioctl	  = NULL,
+	.get_locks	  = arcofi_get_locks,
+};
+
+static const struct audio_format arcofi_formats[] = {
+	/*
+	 * 8-bit encodings:
+	 *  - u-Law and A-Law are native
+	 *  - linear are converted to 16-bit by auconv
+	 */
+	{NULL, AUMODE_PLAY | AUMODE_RECORD, AUDIO_ENCODING_ULAW, 8, 8,
+	 1, AUFMT_MONAURAL, 1, {8000}},
+	{NULL, AUMODE_PLAY | AUMODE_RECORD, AUDIO_ENCODING_ALAW, 8, 8,
+	 1, AUFMT_MONAURAL, 1, {8000}},
+	/*
+	 * 16-bit encodings:
+	 *  - slinear big-endian is native
+	 *  - unsigned or little-endian are converted by auconv
+	 */
+	{NULL, AUMODE_PLAY | AUMODE_RECORD, AUDIO_ENCODING_SLINEAR_BE, 16, 16,
+	 1, AUFMT_MONAURAL, 1, {8000}},
+};
+#define ARCOFI_NFORMATS  __arraycount(arcofi_formats)
+
+/* mixer items */
+#define	ARCOFI_PORT_AUDIO_IN_VOLUME	0	/* line in volume (GR) */
+#define	ARCOFI_PORT_AUDIO_OUT_VOLUME	1	/* line out volume (GX) */
+#define	ARCOFI_PORT_AUDIO_SPKR_VOLUME	2	/* speaker volume (GX) */
+#define	ARCOFI_PORT_AUDIO_IN_MUTE	3	/* line in mute (MIC) */
+#define	ARCOFI_PORT_AUDIO_OUT_MUTE	4	/* line out mute (H out) */
+#define	ARCOFI_PORT_AUDIO_SPKR_MUTE	5	/* line in mute (LS out) */
+/* mixer classes */
+#define	ARCOFI_CLASS_INPUT		6
+#define	ARCOFI_CLASS_OUTPUT		7
+
+/*
+ * Gain programming formulae are a complete mystery to me, and of course
+ * no two chips are compatible - not even the PSB 2163 and PSB 2165
+ * later ARCOFI chips, from the same manufacturer as the PSB 2160!
+ *
+ * Of course, the PSB 2160 datasheet does not give any set of values.
+ * The following table is taken from the HP-UX audio driver (audio_shared.o
+ * private_audio_gain_tab).
+ */
+
+#define	NEGATIVE_GAINS	60
+#define	POSITIVE_GAINS	14
+static const uint16_t arcofi_gains[1 + NEGATIVE_GAINS + 1 + POSITIVE_GAINS] = {
+	/* minus infinity */
+	0x0988,
+
+	0xf8b8, 0xf8b8, 0xf8b8, 0xf8b8, 0x099f, 0x099f, 0x099f, 0x099f,
+	0x09af, 0x09af, 0x09af, 0x09cf, 0x09cf, 0x09cf, 0xf8a9, 0xf83a,
+	0xf83a, 0xf82b, 0xf82d, 0xf8a3, 0xf8b2, 0xf8a1, 0xe8aa, 0xe84b,
+	0xe89e, 0xe8d3, 0xe891, 0xe8b1, 0xd8aa, 0xd8cb, 0xd8a6, 0xd8b3,
+	0xd842, 0xd8b1, 0xc8aa, 0xc8bb, 0xc888, 0xc853, 0xc852, 0xc8b1,
+	0xb8aa, 0xb8ab, 0xb896, 0xb892, 0xb842, 0xb8b1, 0xa8aa, 0xa8bb,
+	0x199f, 0x195b, 0x29c1, 0x2923, 0x29aa, 0x392b, 0xf998, 0xb988,
+	0x1aac, 0x3aa1, 0xbaa1, 0xbb88,
+
+	/* 0 */
+	0x8888,
+
+	0xd388, 0x5288, 0xb1a1, 0x31a1, 0x1192, 0x11d0, 0x30c0, 0x2050,
+	0x1021, 0x1020, 0x1000, 0x0001, 0x0010, 0x0000
+};
+
+static int
+arcofi_open(void *v, int flags)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+	if (sc->sc_open)
+		return EBUSY;
+	sc->sc_open = 1;
+	KASSERT(sc->sc_mode == 0);
+
+	return 0;
+}
+
+static void
+arcofi_close(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+	arcofi_halt_input(v);
+	arcofi_halt_output(v);
+	sc->sc_open = 0;
+}
+
+static int
+arcofi_drain(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+	if ((arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_OUT_EMPTY) == 0) {
+		/* enable output FIFO empty interrupt... */
+		arcofi_write(sc, ARCOFI_FIFO_IR,
+		    arcofi_read(sc, ARCOFI_FIFO_IR) |
+		    FIFO_IR_ENABLE(FIFO_IR_OUT_EMPTY));
+		/* ...and wait for it to fire */
+		if (cv_timedwait(&sc->sc_cv, &sc->sc_intr_lock,
+		    ((ARCOFI_FIFO_SIZE * hz) / 8000) + 100) != 0) {
+			printf("%s: drain did not complete\n",
+			    device_xname(sc->sc_dev));
+			arcofi_write(sc, ARCOFI_FIFO_IR,
+			    arcofi_read(sc, ARCOFI_FIFO_IR) &
+			    ~FIFO_IR_ENABLE(FIFO_IR_OUT_EMPTY));
+		}
+	}
+	return 0;
+}
+
+static int
+arcofi_query_encoding(void *v, struct audio_encoding *aep)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+	return auconv_query_encoding(sc->sc_encodings, aep);
+}
+
+/*
+ * Compute proper sample and hardware settings.
+ */
+static int
+arcofi_set_params(void *handle, int setmode, int usemode,
+    audio_params_t *play, audio_params_t *rec,
+    stream_filter_list_t *pfil, stream_filter_list_t *rfil)
+{
+	struct arcofi_softc *sc;
+	int i;
+
+	sc = handle;
+	for (i = 0; i < 2; i++) {
+		int mode;
+		audio_params_t *p;
+		stream_filter_list_t *fil;
+		int ind;
+
+		switch (i) {
+		case 0:
+			mode = AUMODE_PLAY;
+			p = play;
+			fil = pfil;
+			break;
+		case 1:
+			mode = AUMODE_RECORD;
+			p = rec;
+			fil = rfil;
+			break;
+		default:
+			return EINVAL;
+		}
+
+		if ((setmode & mode) == 0)
+			continue;
+
+#ifdef ARCOFI_DEBUG
+		printf("%s: %s, mode %d encoding %d precision %d\n",
+		    device_xname(sc->sc_dev), __func__,
+		    mode, p->encoding, p->precision);
+#endif
+
+		ind = auconv_set_converter(arcofi_formats, ARCOFI_NFORMATS,
+		    mode, p, false, fil);
+		if (ind < 0)
+			return EINVAL;
+		if (fil->req_size > 0)
+			p = &fil->filters[0].param;
+		if (p->precision == 8) {
+			if (p->encoding == AUDIO_ENCODING_ALAW)
+				sc->sc_shadow.cr4 &= ~CR4_ULAW;
+			else
+				sc->sc_shadow.cr4 |= CR4_ULAW;
+			sc->sc_shadow.cr3 =
+			    (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) |
+			    CR3_OPMODE_NORMAL;
+		} else {
+			sc->sc_shadow.cr3 =
+			    (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) |
+			    CR3_OPMODE_LINEAR;
+		}
+	}
+
+	return 0;
+}
+
+static int
+arcofi_round_blocksize(void *handle, int block, int mode,
+    const audio_params_t *param)
+{
+
+	/*
+	 * Round the size up to a multiple of half the FIFO, to favour
+	 * smooth interrupt operation.
+	 */
+	return roundup(block, ARCOFI_FIFO_SIZE / 2);
+}
+
+static int
+arcofi_commit_settings(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+	int rc;
+	uint8_t cmd[2], csr, ocsr;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, gr %04x gx %04x cr3 %02x cr4 %02x mute %d\n",
+	    device_xname(sc->sc_dev), __func__,
+	    arcofi_gains[sc->sc_shadow.gr_idx],
+	    arcofi_gains[sc->sc_shadow.gx_idx],
+	    sc->sc_shadow.cr3, sc->sc_shadow.cr4, sc->sc_shadow.output_mute);
+#endif
+
+	if (memcmp(&sc->sc_active, &sc->sc_shadow, sizeof(sc->sc_active)) == 0)
+		return 0;
+
+	mutex_spin_enter(&sc->sc_intr_lock);
+
+	if (sc->sc_active.gr_idx != sc->sc_shadow.gr_idx) {
+		cmd[0] = arcofi_gains[sc->sc_shadow.gr_idx] >> 8;
+		cmd[1] = arcofi_gains[sc->sc_shadow.gr_idx];
+		if ((rc = arcofi_cmd(sc, COP_2, cmd)) != 0)
+			goto error;
+		sc->sc_active.gr_idx = sc->sc_shadow.gr_idx;
+	}
+
+	if (sc->sc_active.gx_idx != sc->sc_shadow.gx_idx ||
+	    sc->sc_active.output_mute != sc->sc_shadow.output_mute) {
+		if (sc->sc_shadow.output_mute) {
+			cmd[0] = arcofi_gains[0] >> 8;
+			cmd[1] = arcofi_gains[0];
+		} else {
+			cmd[0] = arcofi_gains[sc->sc_shadow.gx_idx] >> 8;
+			cmd[1] = arcofi_gains[sc->sc_shadow.gx_idx];
+		}
+		if ((rc = arcofi_cmd(sc, COP_B, cmd)) != 0)
+			goto error;
+		sc->sc_active.gx_idx = sc->sc_shadow.gx_idx;
+		sc->sc_active.output_mute = sc->sc_shadow.output_mute;
+	}
+
+	if (sc->sc_active.cr3 != sc->sc_shadow.cr3) {
+		cmd[0] = sc->sc_shadow.cr3;
+		if ((rc = arcofi_cmd(sc, SOP_6, cmd)) != 0)
+			goto error;
+		sc->sc_active.cr3 = sc->sc_shadow.cr3;
+
+		ocsr = arcofi_read(sc, ARCOFI_CSR);
+		if ((sc->sc_active.cr3 & CR3_OPMODE_MASK) != CR3_OPMODE_NORMAL)
+			csr = ocsr | CSR_WIDTH_16;
+		else
+			csr = ocsr & ~CSR_WIDTH_16;
+		if (csr != ocsr)
+			arcofi_write(sc, ARCOFI_CSR, csr);
+	}
+
+	if (sc->sc_active.cr4 != sc->sc_shadow.cr4) {
+		cmd[0] = sc->sc_shadow.cr4;
+		if ((rc = arcofi_cmd(sc, SOP_7, cmd)) != 0)
+			goto error;
+		sc->sc_active.cr4 = sc->sc_shadow.cr4;
+	}
+
+	rc = 0;
+ error:
+	mutex_spin_exit(&sc->sc_intr_lock);
+	return rc;
+}
+
+static int
+arcofi_start_input(void *v, void *rbuf, int rsz, void (*cb)(void *),
+    void *cbarg)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+
+	/* enable data FIFO if becoming active */
+	if (sc->sc_mode == 0)
+		arcofi_write(sc, ARCOFI_CSR,
+		    arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE);
+	sc->sc_mode |= AUMODE_RECORD;
+
+	sc->sc_recv.buf = (uint8_t *)rbuf;
+	sc->sc_recv.past = (uint8_t *)rbuf + rsz;
+	sc->sc_recv.cb = cb;
+	sc->sc_recv.cbarg = cbarg;
+
+	/* enable input FIFO interrupts */
+	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) |
+	    FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));
+
+	return 0;
+}
+
+static int
+arcofi_start_output(void *v, void *wbuf, int wsz, void (*cb)(void *),
+    void *cbarg)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+
+	/* enable data FIFO if becoming active */
+	if (sc->sc_mode == 0)
+		arcofi_write(sc, ARCOFI_CSR,
+		    arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE);
+	sc->sc_mode |= AUMODE_PLAY;
+
+	sc->sc_xmit.buf = (uint8_t *)wbuf;
+	sc->sc_xmit.past = (uint8_t *)wbuf + wsz;
+	sc->sc_xmit.cb = cb;
+	sc->sc_xmit.cbarg = cbarg;
+
+	/* enable output FIFO interrupts */
+	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) |
+	    FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));
+
+	return 0;
+}
+
+static int
+arcofi_halt_input(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+
+	/* disable input FIFO interrupts */
+	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) &
+	    ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));
+	/* disable data FIFO if becoming idle */
+	sc->sc_mode &= ~AUMODE_RECORD;
+	if (sc->sc_mode == 0)
+		arcofi_write(sc, ARCOFI_CSR,
+		    arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE);
+
+	return 0;
+}
+
+static int
+arcofi_halt_output(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+
+	/* disable output FIFO interrupts */
+	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) &
+	    ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));
+	/* disable data FIFO if becoming idle */
+	sc->sc_mode &= ~AUMODE_PLAY;
+	if (sc->sc_mode == 0)
+		arcofi_write(sc, ARCOFI_CSR,
+		    arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE);
+
+	return 0;
+}
+
+static int
+arcofi_getdev(void *v, struct audio_device *ad)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+	*ad = sc->sc_audio_device;
+	return 0;
+}
+
+/*
+ * Convert gain table index to AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale.
+ */
+static int
+arcofi_gain_to_mi(uint idx)
+{
+
+	if (idx == 0)
+		return AUDIO_MIN_GAIN;
+	if (idx == __arraycount(arcofi_gains) - 1)
+		return AUDIO_MAX_GAIN;
+
+	return ((idx - 1) * (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) /
+	    (__arraycount(arcofi_gains) - 1) + AUDIO_MIN_GAIN + 1;
+}
+
+/*
+ * Convert AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale to gain table index.
+ */
+static uint
+arcofi_mi_to_gain(int lvl)
+{
+
+	if (lvl <= AUDIO_MIN_GAIN)
+		return 0;
+	if (lvl >= AUDIO_MAX_GAIN)
+		return __arraycount(arcofi_gains) - 1;
+
+	return ((lvl - AUDIO_MIN_GAIN - 1) * (__arraycount(arcofi_gains) - 1)) /
+	    (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN);
+}
+
+/*
+ * The following routines rely upon this...
+ */
+#if (AUDIO_SPEAKER == AUDIO_LINE_IN) || (AUDIO_LINE_OUT == AUDIO_LINE_IN) || \
+    (AUDIO_SPEAKER == AUDIO_LINE_OUT)
+#error Please rework the cr3 handling logic.
+#endif
+
+/*
+ * The mapping between the available inputs and outputs, and CR3, is as
+ * follows:
+ * - the `line in' connector is the `MIC' input.
+ * - the `line out' connector is the `H out' (heaphones) output.
+ * - the internal `speaker' is the `LS out' (loudspeaker) output.
+ *
+ * Each of these can be enabled or disabled indepently, except for
+ * MIC enabled with H out and LS out disabled, which is not allowed
+ * by the chip (and makes no sense for a chip which was intended to
+ * be used in phones, not voice recorders); we cheat by keeping one
+ * output source enabled, but with the output gain forced to minus
+ * infinity to mute it.
+ *
+ * The truth table is thus:
+ *
+ *	MIC	LS out	H out	AFEC
+ *	off	off	off	POR
+ *	off	off	on	MUTE
+ *	off	on	off	LH1
+ *	off	on	on	LH3, X input enabled
+ *	on	off	off	RDY, GX forced to minus infinity
+ *	on	off	on	RDY
+ *	on	on	off	LH2
+ *	on	on	on	LH3
+ */
+
+/*
+ * Convert logical port enable settings to a valid CR3 value.
+ */
+static uint
+arcofi_portmask_to_cr3(int mask)
+{
+
+	switch (mask) {
+	default:
+	case 0:
+		return CR3_MIC_G_17 | CR3_AFEC_POR;
+	case AUDIO_LINE_OUT:
+		return CR3_MIC_G_17 | CR3_AFEC_MUTE;
+	case AUDIO_SPEAKER:
+		return CR3_MIC_G_17 | CR3_AFEC_LH1;
+	case AUDIO_SPEAKER | AUDIO_LINE_OUT:
+		return CR3_MIC_X_INPUT | CR3_AFEC_LH3;
+	case AUDIO_LINE_IN:
+		/* since we can't do this, just... */
+		/* FALLTHROUGH */
+	case AUDIO_LINE_IN | AUDIO_LINE_OUT:
+		return CR3_MIC_G_17 | CR3_AFEC_RDY;
+	case AUDIO_LINE_IN | AUDIO_SPEAKER:
+		return CR3_MIC_G_17 | CR3_AFEC_LH2;
+	case AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT:
+		return CR3_MIC_G_17 | CR3_AFEC_LH3;
+	}
+}
+
+/*
+ * Convert CR3 to an enabled ports mask.
+ */
+static int
+arcofi_cr3_to_portmask(uint cr3, int output_mute)
+{
+
+	switch (cr3 & CR3_AFEC_MASK) {
+	default:
+	case CR3_AFEC_POR:
+		return 0;
+	case CR3_AFEC_RDY:
+		return output_mute ?
+		    AUDIO_LINE_IN : AUDIO_LINE_IN | AUDIO_LINE_OUT;
+	case CR3_AFEC_HFS:
+	case CR3_AFEC_LH1:
+		return AUDIO_SPEAKER;
+	case CR3_AFEC_LH2:
+		return AUDIO_LINE_IN | AUDIO_SPEAKER;
+	case CR3_AFEC_LH3:
+		if ((cr3 & CR3_MIC_G_MASK) == CR3_MIC_X_INPUT)
+			return AUDIO_SPEAKER | AUDIO_LINE_OUT;
+		else
+			return AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT;
+	case CR3_AFEC_MUTE:
+		return AUDIO_LINE_OUT;
+	}
+}
+
+static int
+arcofi_set_port(void *v, mixer_ctrl_t *mc)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+	int portmask = 0;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+	/* check for proper type */
+	switch (mc->dev) {
+	/* volume settings */
+	case ARCOFI_PORT_AUDIO_IN_VOLUME:
+	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
+	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
+		if (mc->un.value.num_channels != 1)
+			return EINVAL;
+		break;
+	/* mute settings */
+	case ARCOFI_PORT_AUDIO_IN_MUTE:
+	case ARCOFI_PORT_AUDIO_OUT_MUTE:
+	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
+		if (mc->type != AUDIO_MIXER_ENUM)
+			return EINVAL;
+		portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3,
+		    sc->sc_shadow.output_mute);
+#ifdef ARCOFI_DEBUG
+		printf("%s: %s cr3 %02x -> mask %02x\n",
+		    device_xname(sc->sc_dev), __func__,
+		    sc->sc_shadow.cr3, portmask);
+#endif
+		break;
+	default:
+		return EINVAL;
+	}
+
+	switch (mc->dev) {
+	/* volume settings */
+	case ARCOFI_PORT_AUDIO_IN_VOLUME:
+		sc->sc_shadow.gr_idx =
+		    arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]);
+		return 0;
+	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
+	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
+		sc->sc_shadow.gx_idx =
+		    arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]);
+		return 0;
+
+	/* mute settings */
+	case ARCOFI_PORT_AUDIO_IN_MUTE:
+		if (mc->un.ord)
+			portmask &= ~AUDIO_LINE_IN;
+		else
+			portmask |= AUDIO_LINE_IN;
+		break;
+	case ARCOFI_PORT_AUDIO_OUT_MUTE:
+		if (mc->un.ord)
+			portmask &= ~AUDIO_LINE_OUT;
+		else
+			portmask |= AUDIO_LINE_OUT;
+		break;
+	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
+		if (mc->un.ord)
+			portmask &= ~AUDIO_SPEAKER;
+		else
+			portmask |= AUDIO_SPEAKER;
+		break;
+	}
+
+	sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & CR3_OPMODE_MASK) |
+	    arcofi_portmask_to_cr3(portmask);
+	sc->sc_shadow.output_mute = (portmask == AUDIO_LINE_IN);
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s mask %02x -> cr3 %02x m %d\n",
+	    device_xname(sc->sc_dev), __func__,
+	    portmask, sc->sc_shadow.cr3, sc->sc_shadow.output_mute);
+#endif
+
+	return 0;
+}
+
+static int
+arcofi_get_port(void *v, mixer_ctrl_t *mc)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+	int portmask = 0;
+
+#ifdef ARCOFI_DEBUG
+	printf("%s: %s, mode %d\n",
+	    device_xname(sc->sc_dev), __func__, sc->sc_mode);
+#endif
+	/* check for proper type */
+	switch (mc->dev) {
+	/* volume settings */
+	case ARCOFI_PORT_AUDIO_IN_VOLUME:
+	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
+	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
+		if (mc->un.value.num_channels != 1)
+			return EINVAL;
+		break;
+
+	/* mute settings */
+	case ARCOFI_PORT_AUDIO_IN_MUTE:
+	case ARCOFI_PORT_AUDIO_OUT_MUTE:
+	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
+		if (mc->type != AUDIO_MIXER_ENUM)
+			return EINVAL;
+		portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3,
+		    sc->sc_shadow.output_mute);
+#ifdef ARCOFI_DEBUG
+		printf("%s: %s cr3 %02x -> mask %02x\n",
+		    device_xname(sc->sc_dev), __func__,
+		    sc->sc_shadow.cr3, portmask);
+#endif
+		break;
+	default:
+		return EINVAL;
+	}
+
+	switch (mc->dev) {
+	/* volume settings */
+	case ARCOFI_PORT_AUDIO_IN_VOLUME:
+		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
+		    arcofi_gain_to_mi(sc->sc_shadow.gr_idx);
+		break;
+	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
+	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
+		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
+		    arcofi_gain_to_mi(sc->sc_shadow.gx_idx);
+		break;
+
+	/* mute settings */
+	case ARCOFI_PORT_AUDIO_IN_MUTE:
+		mc->un.ord = portmask & AUDIO_LINE_IN ? 0 : 1;
+		break;
+	case ARCOFI_PORT_AUDIO_OUT_MUTE:
+		mc->un.ord = portmask & AUDIO_LINE_OUT ? 0 : 1;
+		break;
+	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
+		mc->un.ord = portmask & AUDIO_SPEAKER ? 0 : 1;
+		break;
+	}
+
+	return 0;
+}
+
+static int
+arcofi_query_devinfo(void *v, mixer_devinfo_t *md)
+{
+
+	switch (md->index) {
+	default:
+		return ENXIO;
+
+	/* items */
+	case ARCOFI_PORT_AUDIO_IN_VOLUME:
+		md->type = AUDIO_MIXER_VALUE;
+		md->mixer_class = ARCOFI_CLASS_INPUT;
+		md->prev = AUDIO_MIXER_LAST;
+		md->next = ARCOFI_PORT_AUDIO_IN_MUTE;
+		strlcpy(md->label.name, AudioNline,
+		    sizeof md->label.name);
+		goto mono_volume;
+	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
+		md->type = AUDIO_MIXER_VALUE;
+		md->mixer_class = ARCOFI_CLASS_OUTPUT;
+		md->prev = AUDIO_MIXER_LAST;
+		md->next = ARCOFI_PORT_AUDIO_OUT_MUTE;
+		strlcpy(md->label.name, AudioNline,
+		    sizeof md->label.name);
+		goto mono_volume;
+	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
+		md->type = AUDIO_MIXER_VALUE;
+		md->mixer_class = ARCOFI_CLASS_OUTPUT;
+		md->prev = AUDIO_MIXER_LAST;
+		md->next = ARCOFI_PORT_AUDIO_SPKR_MUTE;
+		strlcpy(md->label.name, AudioNspeaker,
+		    sizeof md->label.name);
+		/* goto mono_volume; */
+ mono_volume:
+		md->un.v.num_channels = 1;
+		strlcpy(md->un.v.units.name, AudioNvolume,
+		    sizeof md->un.v.units.name);
+		break;
+
+	case ARCOFI_PORT_AUDIO_IN_MUTE:
+		md->type = AUDIO_MIXER_ENUM;
+		md->mixer_class = ARCOFI_CLASS_INPUT;
+		md->prev = ARCOFI_PORT_AUDIO_IN_VOLUME;
+		md->next = AUDIO_MIXER_LAST;
+		goto mute;
+	case ARCOFI_PORT_AUDIO_OUT_MUTE:
+		md->type = AUDIO_MIXER_ENUM;
+		md->mixer_class = ARCOFI_CLASS_OUTPUT;
+		md->prev = ARCOFI_PORT_AUDIO_OUT_VOLUME;
+		md->next = AUDIO_MIXER_LAST;
+		goto mute;
+	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
+		md->type = AUDIO_MIXER_ENUM;
+		md->mixer_class = ARCOFI_CLASS_OUTPUT;
+		md->prev = ARCOFI_PORT_AUDIO_SPKR_VOLUME;
+		md->next = AUDIO_MIXER_LAST;
+		/* goto mute; */
+ mute:
+		strlcpy(md->label.name, AudioNmute, sizeof md->label.name);
+		md->un.e.num_mem = 2;
+		strlcpy(md->un.e.member[0].label.name, AudioNoff,
+		    sizeof md->un.e.member[0].label.name);
+		md->un.e.member[0].ord = 0;
+		strlcpy(md->un.e.member[1].label.name, AudioNon,
+		    sizeof md->un.e.member[1].label.name);
+		md->un.e.member[1].ord = 1;
+		break;
+
+	/* classes */
+	case ARCOFI_CLASS_INPUT:
+		md->type = AUDIO_MIXER_CLASS;
+		md->mixer_class = ARCOFI_CLASS_INPUT;
+		md->prev = AUDIO_MIXER_LAST;
+		md->next = AUDIO_MIXER_LAST;
+		strlcpy(md->label.name, AudioCinputs,
+		    sizeof md->label.name);
+		break;
+	case ARCOFI_CLASS_OUTPUT:
+		md->type = AUDIO_MIXER_CLASS;
+		md->mixer_class = ARCOFI_CLASS_OUTPUT;
+		md->prev = AUDIO_MIXER_LAST;
+		md->next = AUDIO_MIXER_LAST;
+		strlcpy(md->label.name, AudioCoutputs,
+		    sizeof md->label.name);
+		break;
+	}
+
+	return 0;
+}
+
+static int
+arcofi_get_props(void *v)
+{
+
+	return 0;
+}
+
+static void
+arcofi_get_locks(void *v, kmutex_t **intr, kmutex_t **thread)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+
+	*intr = &sc->sc_intr_lock;
+	*thread = &sc->sc_lock;
+}
+
+int
+arcofi_hwintr(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+	uint8_t *cur, *past;
+	uint8_t csr, fir, data;
+	int rc = 0;
+
+	csr = arcofi_read(sc, ARCOFI_CSR);
+	if ((csr & CSR_INTR_REQUEST) == 0)
+		return 0;
+
+	fir = arcofi_read(sc, ARCOFI_FIFO_IR);
+
+	/* receive */
+	if (fir & FIFO_IR_EVENT(FIFO_IR_IN_HALF_EMPTY)) {
+		rc = 1;
+		cur = sc->sc_recv.buf;
+		past = sc->sc_recv.past;
+
+		while ((arcofi_read(sc, ARCOFI_FIFO_SR) &
+		    FIFO_SR_IN_EMPTY) == 0) {
+			data = arcofi_read(sc, ARCOFI_FIFO_DATA);
+			if (cur != NULL && cur != past) {
+				*cur++ = data;
+				if (cur == past) {
+					softint_schedule(sc->sc_sih);
+					break;
+				}
+			}
+		}
+		sc->sc_recv.buf = cur;
+
+		if (cur == NULL || cur == past) {
+			/* underrun, disable further interrupts */
+			arcofi_write(sc, ARCOFI_FIFO_IR,
+			    arcofi_read(sc, ARCOFI_FIFO_IR) &
+			    ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));
+		}
+	}
+
+	/* xmit */
+	if (fir & FIFO_IR_EVENT(FIFO_IR_OUT_HALF_EMPTY)) {
+		rc = 1;
+		cur = sc->sc_xmit.buf;
+		past = sc->sc_xmit.past;
+		if (cur != NULL) {
+			while ((arcofi_read(sc, ARCOFI_FIFO_SR) &
+			    FIFO_SR_OUT_FULL) == 0) {
+				if (cur != past)
+					arcofi_write(sc, ARCOFI_FIFO_DATA,
+					    *cur++);
+				if (cur == past) {
+					softint_schedule(sc->sc_sih);
+					break;
+				}
+			}
+		}
+		if (cur == NULL || cur == past) {
+			/* disable further interrupts */
+			arcofi_write(sc, ARCOFI_FIFO_IR,
+			    arcofi_read(sc, ARCOFI_FIFO_IR) &
+			    ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));
+		}
+		sc->sc_xmit.buf = cur;
+	}
+
+	/* drain */
+	if (fir & FIFO_IR_EVENT(FIFO_IR_OUT_EMPTY)) {
+		rc = 1;
+		arcofi_write(sc, ARCOFI_FIFO_IR,
+		    arcofi_read(sc, ARCOFI_FIFO_IR) &
+		    ~FIFO_IR_ENABLE(FIFO_IR_OUT_EMPTY));
+		mutex_spin_enter(&sc->sc_intr_lock);
+		cv_signal(&sc->sc_cv);
+		mutex_spin_exit(&sc->sc_intr_lock);
+	}
+
+#ifdef ARCOFI_DEBUG
+	if (rc == 0)
+		printf("%s: unclaimed interrupt, csr %02x fir %02x fsr %02x\n",
+		    device_xname(sc->sc_dev), csr, fir,
+		    arcofi_read(sc, ARCOFI_FIFO_SR));
+#endif
+
+	return rc;
+}
+
+void
+arcofi_swintr(void *v)
+{
+	struct arcofi_softc *sc = (struct arcofi_softc *)v;
+	int action;
+
+	action = 0;
+	mutex_spin_enter(&sc->sc_intr_lock);
+	if (sc->sc_recv.buf != NULL && sc->sc_recv.buf == sc->sc_recv.past)
+		action |= AUMODE_RECORD;
+	if (sc->sc_xmit.buf != NULL && sc->sc_xmit.buf == sc->sc_xmit.past)
+		action |= AUMODE_PLAY;
+
+	if (action & AUMODE_RECORD) {
+		if (sc->sc_recv.cb)
+			sc->sc_recv.cb(sc->sc_recv.cbarg);
+	}
+	if (action & AUMODE_PLAY) {
+		if (sc->sc_xmit.cb)
+			sc->sc_xmit.cb(sc->sc_xmit.cbarg);
+	}
+	mutex_spin_exit(&sc->sc_intr_lock);
+}
+
+static int
+arcofi_cmd(struct arcofi_softc *sc, uint8_t cmd, const uint8_t *data)
+{
+	size_t len;
+	uint8_t csr;
+	int cnt;
+	static const uint8_t cmdlen[] = {
+	    [SOP_0] = 4,
+	    [COP_1] = 4,
+	    [COP_2] = 2,
+	    [COP_3] = 2,
+	    [SOP_4] = 1,
+	    [SOP_5] = 1,
+	    [SOP_6] = 1,
+	    [SOP_7] = 1,
+	    [COP_8] = 2,
+	    [COP_9] = 4,
+	    [COP_A] = 8,
+	    [COP_B] = 2,
+	    [COP_C] = 8,
+	    [COP_D] = 4,
+	    [COP_E] = 4
+	};
+
+	/*
+	 * Compute command length.
+	 */
+	if (cmd >= __arraycount(cmdlen))
+		return EINVAL;
+	len = cmdlen[cmd];
+
+	KASSERT(cold || mutex_owned(&sc->sc_intr_lock));
+
+	/*
+	 * Disable all FIFO processing.
+	 */
+	csr = arcofi_read(sc, ARCOFI_CSR);
+	arcofi_write(sc, ARCOFI_CSR,
+	    csr & ~(CSR_DATA_FIFO_ENABLE | CSR_CTRL_FIFO_ENABLE));
+
+	/*
+	 * Fill the FIFO with the command bytes.
+	 */
+	arcofi_write(sc, ARCOFI_FIFO_CTRL, CMDR_PU | CMDR_WRITE | cmd);
+	for (; len != 0; len--)
+		arcofi_write(sc, ARCOFI_FIFO_CTRL, *data++);
+
+	/*
+	 * Enable command processing.
+	 */
+	arcofi_write(sc, ARCOFI_CSR,
+	    (csr & ~CSR_DATA_FIFO_ENABLE) | CSR_CTRL_FIFO_ENABLE);
+
+	/*
+	 * Wait for the command FIFO to be empty.
+	 */
+	cnt = 100;
+	while ((arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_CTRL_EMPTY) == 0) {
+		if (cnt-- == 0) {
+			return EBUSY;
+		}
+		delay(10);
+	}
+
+	arcofi_write(sc, ARCOFI_CSR, csr);
+
+	return 0;
+}
+
+void
+arcofi_attach(struct arcofi_softc *sc, const char *versionstr)
+{
+	device_t self;
+	int rc;
+	uint8_t op, cmd[4];
+
+	self = sc->sc_dev;
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO);
+	cv_init(&sc->sc_cv, device_xname(self));
+	rc = auconv_create_encodings(arcofi_formats, ARCOFI_NFORMATS,
+	    &sc->sc_encodings);
+	if (rc != 0)
+		goto out;
+
+	/*
+	 * Reset logic.
+	 */
+	arcofi_write(sc, ARCOFI_ID, 0);
+	delay(100000);
+	arcofi_write(sc, ARCOFI_CSR, 0);
+
+	/*
+	 * Initialize the chip to default settings (8 bit, u-Law).
+	 */
+	sc->sc_active.cr3 =
+	    arcofi_portmask_to_cr3(AUDIO_SPEAKER) | CR3_OPMODE_NORMAL;
+	sc->sc_active.cr4 = CR4_TM | CR4_ULAW;
+	sc->sc_active.gr_idx = sc->sc_active.gx_idx = 1 + NEGATIVE_GAINS;
+	sc->sc_active.output_mute = 0;
+	memcpy(&sc->sc_shadow, &sc->sc_active, sizeof(sc->sc_active));
+
+	/* clear CRAM */
+	op = SOP_4;
+	cmd[0] = CR1_IDR;
+	if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
+		goto error;
+	delay(1000);
+
+	/* set gain values before enabling them in CR1 */
+	op = COP_2;
+	cmd[0] = arcofi_gains[sc->sc_active.gr_idx] >> 8;
+	cmd[1] = arcofi_gains[sc->sc_active.gr_idx];
+	if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
+		goto error;
+	/* same value for gx... */
+	op = COP_B;
+	if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
+		goto error;
+
+	/* set all CR registers at once */
+	op = SOP_0;
+	cmd[0] = sc->sc_active.cr4;
+	cmd[1] = sc->sc_active.cr3;
+	cmd[2] = CR2_SD | CR2_SC | CR2_SB | CR2_SA | CR2_ELS | CR2_AM | CR2_EFC;
+	cmd[3] = CR1_GR | CR1_GX;
+	if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
+		goto error;
+
+	arcofi_write(sc, ARCOFI_FIFO_IR, 0);
+	arcofi_write(sc, ARCOFI_CSR, CSR_INTR_ENABLE);
+
+	strlcpy(sc->sc_audio_device.name, arcofi_cd.cd_name,
+	    sizeof(sc->sc_audio_device.name));
+	strlcpy(sc->sc_audio_device.version, versionstr,
+	    sizeof(sc->sc_audio_device.version));
+	strlcpy(sc->sc_audio_device.config, device_xname(self),
+	    sizeof(sc->sc_audio_device.config));
+
+	audio_attach_mi(&arcofi_hw_if, sc, self);
+	return;
+
+ error:
+	arcofi_write(sc, ARCOFI_ID, 0);
+	aprint_error("%s: %02x command failed, error %d\n",
+	    __func__, op, rc);
+ out:
+	cv_destroy(&sc->sc_cv);
+	mutex_destroy(&sc->sc_intr_lock);
+	mutex_destroy(&sc->sc_lock);
+}
Index: src/sys/dev/ic/arcofivar.h
diff -u /dev/null src/sys/dev/ic/arcofivar.h:1.1.2.2
--- /dev/null	Fri Aug 29 11:42:16 2014
+++ src/sys/dev/ic/arcofivar.h	Fri Aug 29 11:42:15 2014
@@ -0,0 +1,56 @@
+/*	$NetBSD: arcofivar.h,v 1.1.2.2 2014/08/29 11:42:15 martin Exp $	*/
+/*	$OpenBSD: arcofivar.h,v 1.2 2011/12/25 00:07:27 miod Exp $	*/
+
+/*
+ * Copyright (c) 2011 Miodrag Vallat.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define	ARCOFI_NREGS		6
+
+struct arcofi_softc {
+	device_t		sc_dev;
+	bus_addr_t		sc_reg[ARCOFI_NREGS];
+	bus_space_tag_t		sc_iot;
+	bus_space_handle_t	sc_ioh;
+
+	struct audio_device	sc_audio_device;
+	void			*sc_sih;
+
+	int			sc_open;
+	int			sc_mode;
+
+	struct {
+		uint8_t	cr3, cr4;
+		uint	gr_idx, gx_idx;
+		int	output_mute;
+	}			sc_active,
+				sc_shadow;
+
+	struct {
+		uint8_t	*buf;
+		uint8_t	*past;
+		void	(*cb)(void *);
+		void	*cbarg;
+	}			sc_recv,
+				sc_xmit;
+	kmutex_t		sc_lock;
+	kmutex_t		sc_intr_lock;
+	kcondvar_t		sc_cv;
+	struct audio_encoding_set *sc_encodings;
+};
+
+void	arcofi_attach(struct arcofi_softc *, const char *);
+int	arcofi_hwintr(void *);
+void	arcofi_swintr(void *);

Reply via email to