Subject: port-i386/1179: wss driver improvements
To: None <gnats-admin@sun-lamp.pc.cs.cmu.edu>
From: Mike Long <mikel@shore.net>
List: netbsd-bugs
Date: 06/30/1995 02:20:05
>Number:         1179
>Category:       port-i386
>Synopsis:       slightly debugged wss.c and 1848.c drivers
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    gnats-admin (GNATS administrator)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Jun 30 02:20:02 1995
>Originator:     Mike Long <mike.long@analog.com>
>Organization:
	Just say Ack!
>Release:        6/29/95
>Environment:
System: NetBSD azathoth 1.0A NetBSD 1.0A (AZATHOTH) #0: Sat Jun 24 15:40:37 EDT 1995 root@azathoth:/usr/src/sys/arch/i386/compile/AZATHOTH i386


>Description:
	The Windows Sound System/AD1848 driver is broken.
>How-To-Repeat:
	build a GENERIC kernel and boot it on a system containing a WSS.
>Fix:
	All right, here's what I did:

1) removed references to <i386/isa/icu.h>; not needed
2) fixed broken wssprobe()
3) addressing in ad1848* changed to put the index register at base+0
instead of base+4.  The parent device is responsible for keeping its
own iobase.
4) ad1848 iobase check now only checks alignment.
5) started adding support for more codecs (AD1845).

...plus cleanup, comments, etc.  As you can see from the timestamps, I
haven't had a lot of time to work on this recently.


--- /usr/src/sys/dev/isa/wssreg.h.orig  Thu May 11 06:26:32 1995
+++ /usr/src/sys/dev/isa/wssreg.h       Sat Jun  3 13:41:55 1995
@@ -42,20 +42,23 @@
 /*
  * Macros to detect valid hardware configuration data.
  */
-#define WSS_IRQ_VALID(mask)  ((mask) & 0x00ac) /* IRQ 2,3,5,7 */
 #define WSS_BASE_VALID(base) ((base) == 0x0530 || \
                              (base) == 0x0604 || \
                              (base) == 0x0e80 || \
                              (base) == 0x0f40)
+#define WSS_IRQ_VALID(irq)   ((irq) ==  7 || (irq) ==  9 || \
+                             (irq) == 10 || (irq) == 11)
+#define WSS_DRQ_VALID(chan)  ((chan) == 0 || (chan) == 1 || (chan) == 3)
 
 /* Default WSS base */
-/* WSS registers */
 #define WSS_BASE_ADDRESS 0x0530
 
 /* WSS registers */
-#define WSS_CONFIG     0x00
-#define WSS_STATUS     0x03
+#define WSS_CONFIG     0x00    /* write only */
+#define WSS_STATUS     0x03    /* read only */
 
-#define WSS_VERS       0x04
+/* WSS status register bits */
 #define WSS_16SLOT     0x80
-#define WSS_DUALDMA    0x40
+#define WSS_IRQACTIVE  0x40
+#define WSS_VERS       0x04
+#define WSS_VERSMASK   0x3f


--- /usr/src/sys/dev/isa/wss.c.orig     Thu May 11 06:26:31 1995
+++ /usr/src/sys/dev/isa/wss.c  Wed Jun 14 03:34:17 1995
@@ -52,7 +52,6 @@
 
 #include <dev/isa/isavar.h>
 #include <dev/isa/isadmavar.h>
-#include <i386/isa/icu.h>                      /* XXX BROKEN; WHY? */
 
 #include <dev/isa/ad1848var.h>
 #include <dev/isa/ad1848reg.h>
@@ -91,7 +90,6 @@
        void    *sc_ih;                 /* interrupt vectoring */
 
        struct  ad1848_softc sc_ad1848;
-#define wss_iobase sc_ad1848.sc_iobase
 #define wss_irq    sc_ad1848.sc_irq
 #define wss_drq    sc_ad1848.sc_drq
 
@@ -186,19 +184,27 @@
        -1, -1, -1, -1, -1, -1, -1, 0x08, -1, 0x10, 0x18, 0x20
     };
     static u_char dma_bits[4] = {1, 2, 0, 3};
-    char bits;
     
     if (!WSS_BASE_VALID(ia->ia_iobase)) {
        printf("wss: configured iobase %d invalid\n", ia->ia_iobase);
        return 0;
     }
 
-    sc->wss_iobase = iobase;
+    sc->sc_ad1848.sc_iobase = iobase + 4;
 
-    /* Is there an ad1848 chip at the WSS iobase ? */
+    /* Is there an ad1848 chip at the WSS iobase + 4? */
     if (ad1848_probe(&sc->sc_ad1848) == 0)
        return 0;
        
+    ia->ia_iosize = WSS_NPORT;
+
+    /* Setup WSS interrupt and DMA */
+    if (!WSS_DRQ_VALID(ia->ia_drq)) {
+       printf("wss: configured dma chan %d invalid\n", ia->ia_drq);
+       return 0;
+    }
+    sc->wss_drq = ia->ia_drq;
+
 #ifdef NEWCONFIG
     /*
      * If the IRQ wasn't compiled in, auto-detect it.
@@ -206,27 +212,21 @@
     if (ia->ia_irq == IRQUNK) {
        ia->ia_irq = isa_discoverintr(ad1848_forceintr, &sc->sc_ad1848);
        if (!WSS_IRQ_VALID(ia->ia_irq)) {
-           printf("wss: couldn't auto-detect interrupt");
+           printf("wss: couldn't auto-detect interrupt\n");
            return 0;
        }
     }
     else
 #endif
-    ia->ia_iosize = WSS_NPORT;
-
-    /* Setup WSS interrupt and DMA */
-    if ((bits = interrupt_bits[sc->wss_irq]) == -1) {
-           printf("wss: invalid interrupt configuration (irq=%d)\n", sc->wss_irq);
-           return 0;
+    if (!WSS_IRQ_VALID(ia->ia_irq)) {
+       printf("wss: configured interrupt %d invalid\n", ia->ia_irq);
+       return 0;
     }
 
-#if 0
-    /* XXX Dual-DMA */
-    outb(sc->wss_iobase+WSS_CONFIG, (bits | 0x40));
-    if ((inb(sc->wss_iobase+WSS_STATUS) & 0x40) == 0)
-       printf("wss: IRQ?\n");
-#endif    
-    outb(sc->wss_iobase+WSS_CONFIG, (bits | dma_bits[sc->wss_drq]));
+    sc->wss_irq = ia->ia_irq;
+
+    outb(iobase+WSS_CONFIG,
+        (interrupt_bits[ia->ia_irq] | dma_bits[ia->ia_drq]));
 
     return 1;
 }
@@ -244,9 +244,6 @@
     struct isa_attach_args *ia = (struct isa_attach_args *)aux;
     register u_short iobase = ia->ia_iobase;
 
-    sc->wss_iobase = iobase;
-    sc->wss_drq = ia->ia_drq;
-
 #ifdef NEWCONFIG
     isa_establish(&sc->sc_id, &sc->sc_dev);
 #endif
@@ -255,7 +252,7 @@
 
     ad1848_attach(&sc->sc_ad1848);
     
-    printf(" (vers %d)", inb(sc->wss_iobase+WSS_STATUS) & 0x1f);
+    printf(" (vers %d)", inb(iobase+WSS_STATUS) & WSS_VERSMASK);
     printf("\n");
 
     sc->sc_ad1848.parent = sc;


--- /usr/src/sys/dev/isa/ad1848reg.h.orig       Thu May 11 06:26:28 1995
+++ /usr/src/sys/dev/isa/ad1848reg.h    Wed Jun 14 03:03:06 1995
@@ -38,13 +38,14 @@
  * Copyright (c) 1993 Analog Devices Inc. All rights reserved
  */
 
-#define AD1848_BASE_VALID(base) ((base) == 0x530)
+/* parent driver is primarily responsible for checking this */
+#define AD1848_BASE_VALID(base) (((base) & 0x003) == 0)
 
 /* ad1848 direct registers */
-#define AD1848_IADDR   0x04
-#define AD1848_IDATA   0x05
-#define AD1848_STATUS  0x06
-#define AD1848_PIO     0x07
+#define AD1848_IADDR   0x00
+#define AD1848_IDATA   0x01
+#define AD1848_STATUS  0x02
+#define AD1848_PIO     0x03
 
 /* Gain constants  */
 #define GAIN_0      0x00
@@ -63,7 +64,6 @@
 #define GAIN_19_5   0x0d
 #define GAIN_21     0x0e
 #define GAIN_22_5   0x0f
-#define MUTE       0XFFFF
 
 /* Attenuation constants  */
 
@@ -90,7 +90,7 @@
 #define MODE_CHANGE_MASK      0xbf
 #define TRANSFER_DISABLE      0x20
 #define TRANSFER_DISABLE_MASK 0xdf
-#define ADDRESS_MASK          0xf0
+#define ADDRESS_MASK          0xe0
 
 /* Status bits */
 #define INTERRUPT_STATUS      0x01
@@ -99,9 +99,7 @@
 /* pbright is not left */
 #define PLAYBACK_UPPER        0x08
 /* bplower is not upper */
-
-#define SAMPLE_OVERRUN        0x10
-#define SAMPLE_UNDERRUN       0x10
+#define SAMPLE_ERROR          0x10
 #define CAPTURE_READY         0x20
 #define CAPTURE_LEFT          0x40
 /* cpright is not left */
@@ -126,22 +124,19 @@
 #define OUTPUT_ATTEN_BITS     0x3f
 #define OUTPUT_ATTEN_MASK     0xc0
 
-/* Clock and Data format reg bits */
+/* Clock and Data format reg bits (some also Capture Data format) */
 #define CLOCK_SELECT_MASK     0xfe
 #define CLOCK_XTAL2           0x01
 #define CLOCK_XTAL1           0x00
 #define CLOCK_FREQ_MASK       0xf1
 #define STEREO_MONO_MASK      0xef
 #define FMT_STEREO            0x10
-#define RMT_MONO              0x00
-#define LINEAR_COMP_MASK      0xdf
-#define LINEAR                0x00
-#define COMPANDED             0x20
-#define FORMAT_MASK           0xbf
-#define PCM                   0x00
-#define ULAW                  0x00
-#define TWOS_COMP             0x40
-#define ALAW                  0x40
+#define FMT_MONO              0x00
+#define FORMAT_MASK           0x1f
+#define FMT_PCM8              0x00
+#define FMT_ULAW              0x20
+#define FMT_PCM16             0x40
+#define FMT_ALAW              0x60
 
 /* Interface Configuration reg bits */
 #define PLAYBACK_ENABLE       0x01
@@ -170,31 +165,49 @@
 #define DATA_REQUEST_STATUS   0x10
 #define AUTO_CAL_IN_PROG      0x20
 #define PLAYBACK_UNDERRUN     0x40
-#define CAPTURE_UNDERRUN      0x80
+#define CAPTURE_OVERRUN       0x80
 
 /* Miscellaneous Control reg bits */
-#define ID_MASK               0xf0
+#define ID_MASK               0x70
+#define MODE2                 0x40
 
 /* Digital Mix Control reg bits */
 #define DIGITAL_MIX1_MUTE_MASK 0xfe
 #define DIGITAL_MIX1_ENABLE    0x01
 #define MIX_ATTEN_MASK         0xfc
 
-/* 1848 Sound Port reg defines */
-#define SP_LEFT_INPUT_CONTROL    0x0
-#define SP_RIGHT_INPUT_CONTROL   0x1
-#define SP_LEFT_AUX1_CONTROL     0x2
-#define SP_RIGHT_AUX1_CONTROL    0x3
-#define SP_LEFT_AUX2_CONTROL     0x4
-#define SP_RIGHT_AUX2_CONTROL    0x5
-#define SP_LEFT_OUTPUT_CONTROL   0x6
-#define SP_RIGHT_OUTPUT_CONTROL  0x7
-#define SP_CLOCK_DATA_FORMAT     0x8
-#define SP_INTERFACE_CONFIG      0x9
-#define SP_PIN_CONTROL           0xA
-#define SP_TEST_AND_INIT         0xB
-#define SP_MISC_INFO             0xC
-#define SP_DIGITAL_MIX           0xD
-#define SP_UPPER_BASE_COUNT      0xE
-#define SP_LOWER_BASE_COUNT      0xF
-
+/* AD1848 Sound Port reg defines */
+#define SP_LEFT_INPUT_CONTROL    0x00
+#define SP_RIGHT_INPUT_CONTROL   0x01
+#define SP_LEFT_AUX1_CONTROL     0x02
+#define SP_RIGHT_AUX1_CONTROL    0x03
+#define SP_LEFT_AUX2_CONTROL     0x04
+#define SP_RIGHT_AUX2_CONTROL    0x05
+#define SP_LEFT_OUTPUT_CONTROL   0x06
+#define SP_RIGHT_OUTPUT_CONTROL  0x07
+#define SP_CLOCK_DATA_FORMAT     0x08
+#define SP_INTERFACE_CONFIG      0x09
+#define SP_PIN_CONTROL           0x0A
+#define SP_TEST_AND_INIT         0x0B
+#define SP_MISC_INFO             0x0C
+#define SP_DIGITAL_MIX           0x0D
+#define SP_UPPER_BASE_COUNT      0x0E
+#define SP_LOWER_BASE_COUNT      0x0F
+
+/* CS4231/AD1845 mode2 reg defines */
+#define SP_ALT1_LEFT_MIC_CONTROL  0x10
+#define SP_ALT2_RIGHT_MIC_CONTROL 0x11
+#define SP_LEFT_LINE_CONTROL      0x12
+#define SP_RIGHT_LINE_CONTROL     0x13
+#define SP_TIMER_LOWER_BASE       0x14
+#define SP_TIMER_UPPER_BASE       0x15
+#define SP_UPPER_FREQUENCY_SEL    0x16
+#define SP_LOWER_FREQUENCY_SEL    0x17
+#define SP_ALT_FEATURE_STATUS     0x18
+#define SP_VERSION_ID             0x19
+#define SP_MONO_CONTROL           0x1A
+#define SP_POWERDOWN_CONTROL      0x1B
+#define SP_CAPTURE_DATA_FORMAT    0x1C
+#define SP_XTAL_SELECT            0x1D
+#define SP_CAPTURE_UPPER_BASE     0x1E
+#define SP_CAPTURE_LOWER_BASE     0x1F


--- /usr/src/sys/dev/isa/ad1848.c.orig  Thu May 11 06:26:27 1995
+++ /usr/src/sys/dev/isa/ad1848.c       Wed Jun 14 03:52:07 1995
@@ -88,7 +88,6 @@
 
 #include <dev/isa/isavar.h>
 #include <dev/isa/isadmavar.h>
-#include <i386/isa/icu.h>                      /* XXX BROKEN; WHY? */
 
 #include <dev/isa/ad1848reg.h>
 #include <dev/isa/ad1848var.h>
@@ -117,7 +116,7 @@
     0x19,              /* Left DAC Control */
     0x19,              /* Right DAC Control */
                        /* Clock and Data Format */
-    CLOCK_XTAL1|LINEAR|PCM,
+    CLOCK_XTAL1|FMT_PCM8,
                        /* Interface Config */
     SINGLE_DMA,
     INTERRUPT_ENABLE,  /* Pin control */
@@ -268,7 +267,7 @@
     int i;
     
     if (!AD1848_BASE_VALID(iobase)) {
-       printf("ad1848: configured iobase %d invalid\n", iobase);
+       printf("ad1848: configured iobase %#x invalid\n", iobase);
        return 0;
     }
 
@@ -276,8 +275,7 @@
 
     /* Is there an ad1848 chip ? */
     sc->MCE_bit = MODE_CHANGE_ENABLE;
-    sc->chip_name = "ad1848";
-    sc->mode = 1;      /* MODE 1 = original ad1849 */
+    sc->mode = 1;      /* MODE 1 = original ad1848/ad1846/cs4248 */
     
     /*
      * Check that the I/O address is in use.
@@ -321,7 +319,7 @@
      * try to change them.
      */
     tmp = ad_read(sc, SP_MISC_INFO);
-    ad_write(sc, SP_MISC_INFO, (~tmp) & 0x0f);
+    ad_write(sc, SP_MISC_INFO, tmp ^ 0x0f);
 
     if ((tmp & 0x0f) != ((tmp1 = ad_read(sc, SP_MISC_INFO)) & 0x0f)) {
        DPRINTF(("ad_detect_D (%x)\n", tmp1));
@@ -329,23 +327,47 @@
     }
 
     /*
-     * NOTE! Last 4 bits of the reg I12 tell the chip revision.
-     *  0x01=RevB and 0x0A=RevC.
+     * MSB and 4 LSBs of the reg I12 tell the chip revision.
+     *
+     * A preliminary version of the AD1846 data sheet stated that it
+     * used an ID field of 0x0B.  The current version, however,
+     * states that the AD1846 uses ID 0x0A, just like the AD1848K.
+     *
+     * this switch statement will need updating as newer clones arrive....
      */
-    sc->rev = tmp1 & 0x0f;
+    sc->rev = tmp1 & 0x8f;
     switch (sc->rev) {
-    case 11:
-       sc->chip_name = "ad1846";
-       sc->rev = 0;
+    case 0x09:
+       sc->chip_name = "AD1848J";
+       break;
+    case 0x0A:
+       sc->chip_name = "AD1848K";
+       break;
+#if 0
+    case 0x0B:
+       sc->chip_name = "AD1846";
+       break;
+#endif
+    case 0x81:
+       sc->chip_name = "CS4248revB"; /* or CS4231 rev B; see below */
+       break;
+    case 0x89:
+       sc->chip_name = "CS4248";
+       break;
+    case 0x8A:
+       sc->chip_name = "broken"; /* CS4231/AD1845; see below */
+       break;
+    default:
+       sc->chip_name = "unknown";
+       DPRINTF(("ad1848: unknown codec version %#02X\n", sc->rev));
        break;
     }  
-    
 
     /*
-     * The original AD1848/CS4248 has just 15 indirect registers. This means
+     * The original AD1848/CS4248 has just 16 indirect registers. This means
      * that I0 and I16 should return the same value (etc.).
      * Ensure that the Mode2 enable bit of I12 is 0. Otherwise this test fails
-     * with CS4231.
+     * with CS4231, AD1845, etc.
      */
     ad_write(sc, SP_MISC_INFO, 0);     /* Mode2 = disabled */
 
@@ -357,34 +379,48 @@
 
     /*
      * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit (0x40)
-     * The bit 0x80 is always 1 in CS4248 and CS4231.
+     * The bit 0x80 is always 1 in CS4248, CS4231, and AD1845.
      */
-    ad_write(sc, SP_MISC_INFO, 0x40);  /* Set mode2, clear 0x80 */
+    ad_write(sc, SP_MISC_INFO, MODE2);
 
     tmp1 = ad_read(sc, SP_MISC_INFO);
-    if (tmp1 & 0x80)
-       sc->chip_name = "cs4248";
 
-    if ((tmp1 & 0xc0) == (0x80 | 0x40)) {
+    if ((tmp1 & 0xc0) == (0x80 | MODE2)) {
        /*
-        *      CS4231 detected - is it?
+        *      CS4231 or AD1845 detected - is it?
         *
-        *      Verify that setting I0 doesn't change I16.
+        *      Verify that setting I2 doesn't change I18.
         */
-       ad_write(sc, 16, 0);    /* Set I16 to known value */
+       ad_write(sc, 18, 0x88); /* Set I18 to known value */
 
-       ad_write(sc, 0, 0x45);
-       if ((tmp1 = ad_read(sc, 16)) != 0x45) { /* No change -> CS4231? */
-           ad_write(sc, 0, 0xaa);
-           if ((tmp1 = ad_read(sc, 16)) == 0xaa) {     /* Rotten bits? */
-               DPRINTF(("ad_detect_H(%x)\n", tmp1));
+       ad_write(sc, 2, 0x45);
+       if ((tmp2 = ad_read(sc, 18)) != 0x45) { /* No change -> CS4231? */
+           ad_write(sc, 2, 0xaa);
+           if ((tmp2 = ad_read(sc, 18)) == 0xaa) {     /* Rotten bits? */
+               DPRINTF(("ad_detect_H(%x)\n", tmp2));
                return 0;
            }
 
            /*
-            *  It's a CS4231
+            *  It's a CS4231, or another clone with 32 registers.
+            *  Let's find out which by checking I25.
             */
-           sc->chip_name = "cs4231";
+           if ((tmp1 & 0x0f) == 0x8a) {
+               tmp1 = ad_read(sc, SP_VERSION_ID);
+               sc->rev |= (tmp1 & 0xe7);
+               switch (tmp1 & 0xe7) {
+               case 0xA0:
+                   sc->chip_name = "CS4231A";
+                   break;
+               case 0x80:
+                   /*  XXX I25 no good, AD1845 same as CS4231 */
+                   sc->chip_name = "CS4231 or AD1845";
+                   break;
+               case 0x82:
+                   sc->chip_name = "CS4232";
+                   break;
+               }
+           }
            sc->mode = 2;
        }
     }
@@ -414,9 +450,12 @@
     
     sc->sc_locked = 0;
 
+/* blows up when you hit I8, clock & data format */
+#if 0
     /* Initialize the ad1848 */
     for (i = 0; i < 16; i++)
        ad_write(sc, i, ad1848_init_values[i]);
+#endif
 
     ad1848_reset(sc);
 
@@ -452,7 +491,7 @@
     /* Set default port */
     (void) ad1848_set_rec_port(sc, MIC_IN_PORT);
 
-    printf(": %s%c", sc->chip_name, (sc->rev)?'A'+sc->rev:' ');
+    printf(": %s", sc->chip_name);
 }
 
 /*
@@ -464,8 +503,8 @@
     struct ad1848_volume *gp;
 {
     register u_char reg, gain;
-    
-    DPRINTF(("ad1848_set_in_gain: %d:%d\n", gp->left, gp->right));
+
+    DPRINTF(("ad1848_set_rec_gain: %d:%d\n", gp->left, gp->right));
 
     sc->rec_gain = *gp;
 

--- /usr/src/sys/dev/isa/pss.c.orig     Thu May 11 06:26:30 1995
+++ /usr/src/sys/dev/isa/pss.c  Sat Jun  3 13:33:02 1995
@@ -802,7 +802,7 @@
     u_char bits;
     int i;
 
-    sc->sc_iobase = cf->cf_iobase;
+    sc->sc_iobase = cf->cf_iobase + 4;
     
     /* Set WSS io address */
     pss_setaddr(sc->sc_iobase, pc->sc_iobase+PSS_WSS_CONFIG);

>Audit-Trail:
>Unformatted: