]> Pileus Git - ~andy/linux/blobdiff - sound/isa/wss/wss_lib.c
Merge branch 'master' into next
[~andy/linux] / sound / isa / wss / wss_lib.c
index a982997805c4f74fffa2b00178c1d8f5669b978b..3d6c5f2838af41156d7cd8d07f480e58f7dd6e04 100644 (file)
@@ -33,6 +33,7 @@
 #include <sound/core.h>
 #include <sound/wss.h>
 #include <sound/pcm_params.h>
+#include <sound/tlv.h>
 
 #include <asm/io.h>
 #include <asm/dma.h>
@@ -281,7 +282,7 @@ static void snd_wss_debug(struct snd_wss *chip)
        printk(KERN_DEBUG
                "CS4231 REGS:      INDEX = 0x%02x  "
                "                 STATUS = 0x%02x\n",
-                                       wss_inb(chip, CS4231P(REGSEL),
+                                       wss_inb(chip, CS4231P(REGSEL)),
                                        wss_inb(chip, CS4231P(STATUS)));
        printk(KERN_DEBUG
                "  0x00: left input      = 0x%02x  "
@@ -379,7 +380,7 @@ static void snd_wss_busy_wait(struct snd_wss *chip)
        for (timeout = 5; timeout > 0; timeout--)
                wss_inb(chip, CS4231P(REGSEL));
        /* end of cleanup sequence */
-       for (timeout = 250;
+       for (timeout = 25000;
             timeout > 0 && (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT);
             timeout--)
                udelay(10);
@@ -412,6 +413,7 @@ void snd_wss_mce_down(struct snd_wss *chip)
        unsigned long flags;
        unsigned long end_time;
        int timeout;
+       int hw_mask = WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK | WSS_HW_AD1848;
 
        snd_wss_busy_wait(chip);
 
@@ -426,10 +428,8 @@ void snd_wss_mce_down(struct snd_wss *chip)
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        if (timeout == 0x80)
                snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port);
-       if ((timeout & CS4231_MCE) == 0 ||
-           !(chip->hardware & (WSS_HW_CS4231_MASK | WSS_HW_CS4232_MASK))) {
+       if ((timeout & CS4231_MCE) == 0 || !(chip->hardware & hw_mask))
                return;
-       }
 
        /*
         * Wait for (possible -- during init auto-calibration may not be set)
@@ -574,7 +574,7 @@ static void snd_wss_calibrate_mute(struct snd_wss *chip, int mute)
 {
        unsigned long flags;
 
-       mute = mute ? 1 : 0;
+       mute = mute ? 0x80 : 0;
        spin_lock_irqsave(&chip->reg_lock, flags);
        if (chip->calibrate_mute == mute) {
                spin_unlock_irqrestore(&chip->reg_lock, flags);
@@ -589,32 +589,34 @@ static void snd_wss_calibrate_mute(struct snd_wss *chip, int mute)
                             chip->image[CS4231_LOOPBACK]);
        }
        snd_wss_dout(chip, CS4231_AUX1_LEFT_INPUT,
-                    mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]);
+                    mute | chip->image[CS4231_AUX1_LEFT_INPUT]);
        snd_wss_dout(chip, CS4231_AUX1_RIGHT_INPUT,
-                    mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]);
+                    mute | chip->image[CS4231_AUX1_RIGHT_INPUT]);
        snd_wss_dout(chip, CS4231_AUX2_LEFT_INPUT,
-                    mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]);
+                    mute | chip->image[CS4231_AUX2_LEFT_INPUT]);
        snd_wss_dout(chip, CS4231_AUX2_RIGHT_INPUT,
-                    mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]);
+                    mute | chip->image[CS4231_AUX2_RIGHT_INPUT]);
        snd_wss_dout(chip, CS4231_LEFT_OUTPUT,
-                    mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]);
+                    mute | chip->image[CS4231_LEFT_OUTPUT]);
        snd_wss_dout(chip, CS4231_RIGHT_OUTPUT,
-                    mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]);
-       snd_wss_dout(chip, CS4231_LEFT_LINE_IN,
-                    mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]);
-       snd_wss_dout(chip, CS4231_RIGHT_LINE_IN,
-                    mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]);
-       snd_wss_dout(chip, CS4231_MONO_CTRL,
-                    mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+                    mute | chip->image[CS4231_RIGHT_OUTPUT]);
+       if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+               snd_wss_dout(chip, CS4231_LEFT_LINE_IN,
+                            mute | chip->image[CS4231_LEFT_LINE_IN]);
+               snd_wss_dout(chip, CS4231_RIGHT_LINE_IN,
+                            mute | chip->image[CS4231_RIGHT_LINE_IN]);
+               snd_wss_dout(chip, CS4231_MONO_CTRL,
+                            mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]);
+       }
        if (chip->hardware == WSS_HW_INTERWAVE) {
                snd_wss_dout(chip, CS4231_LEFT_MIC_INPUT,
-                            mute ? 0x80 : chip->image[CS4231_LEFT_MIC_INPUT]);
+                            mute | chip->image[CS4231_LEFT_MIC_INPUT]);
                snd_wss_dout(chip, CS4231_RIGHT_MIC_INPUT,
-                            mute ? 0x80 : chip->image[CS4231_RIGHT_MIC_INPUT]);
+                            mute | chip->image[CS4231_RIGHT_MIC_INPUT]);
                snd_wss_dout(chip, CS4231_LINE_LEFT_OUTPUT,
-                       mute ? 0x80 : chip->image[CS4231_LINE_LEFT_OUTPUT]);
+                            mute | chip->image[CS4231_LINE_LEFT_OUTPUT]);
                snd_wss_dout(chip, CS4231_LINE_RIGHT_OUTPUT,
-                       mute ? 0x80 : chip->image[CS4231_LINE_RIGHT_OUTPUT]);
+                            mute | chip->image[CS4231_LINE_RIGHT_OUTPUT]);
        }
        chip->calibrate_mute = mute;
        spin_unlock_irqrestore(&chip->reg_lock, flags);
@@ -705,7 +707,10 @@ static void snd_wss_capture_format(struct snd_wss *chip,
                        snd_wss_mce_up(chip);
                        spin_lock_irqsave(&chip->reg_lock, flags);
                }
-               snd_wss_out(chip, CS4231_REC_FORMAT, cdfr);
+               if (chip->hardware & WSS_HW_AD1848_MASK)
+                       snd_wss_out(chip, CS4231_PLAYBK_FORMAT, cdfr);
+               else
+                       snd_wss_out(chip, CS4231_REC_FORMAT, cdfr);
                spin_unlock_irqrestore(&chip->reg_lock, flags);
                snd_wss_mce_down(chip);
        }
@@ -817,7 +822,9 @@ static void snd_wss_init(struct snd_wss *chip)
 
        snd_wss_mce_up(chip);
        spin_lock_irqsave(&chip->reg_lock, flags);
-       snd_wss_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]);
+       if (!(chip->hardware & WSS_HW_AD1848_MASK))
+               snd_wss_out(chip, CS4231_REC_FORMAT,
+                           chip->image[CS4231_REC_FORMAT]);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        snd_wss_mce_down(chip);
 
@@ -843,20 +850,24 @@ static int snd_wss_open(struct snd_wss *chip, unsigned int mode)
        }
        /* ok. now enable and ack CODEC IRQ */
        spin_lock_irqsave(&chip->reg_lock, flags);
-       snd_wss_out(chip, CS4231_IRQ_STATUS,
-                   CS4231_PLAYBACK_IRQ |
-                   CS4231_RECORD_IRQ |
-                   CS4231_TIMER_IRQ);
-       snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+       if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+               snd_wss_out(chip, CS4231_IRQ_STATUS,
+                           CS4231_PLAYBACK_IRQ |
+                           CS4231_RECORD_IRQ |
+                           CS4231_TIMER_IRQ);
+               snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+       }
        wss_outb(chip, CS4231P(STATUS), 0);     /* clear IRQ */
        wss_outb(chip, CS4231P(STATUS), 0);     /* clear IRQ */
        chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE;
        snd_wss_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]);
-       snd_wss_out(chip, CS4231_IRQ_STATUS,
-                   CS4231_PLAYBACK_IRQ |
-                   CS4231_RECORD_IRQ |
-                   CS4231_TIMER_IRQ);
-       snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+       if (!(chip->hardware & WSS_HW_AD1848_MASK)) {
+               snd_wss_out(chip, CS4231_IRQ_STATUS,
+                           CS4231_PLAYBACK_IRQ |
+                           CS4231_RECORD_IRQ |
+                           CS4231_TIMER_IRQ);
+               snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+       }
        spin_unlock_irqrestore(&chip->reg_lock, flags);
 
        chip->mode = mode;
@@ -878,7 +889,8 @@ static void snd_wss_close(struct snd_wss *chip, unsigned int mode)
 
        /* disable IRQ */
        spin_lock_irqsave(&chip->reg_lock, flags);
-       snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+       if (!(chip->hardware & WSS_HW_AD1848_MASK))
+               snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
        wss_outb(chip, CS4231P(STATUS), 0);     /* clear IRQ */
        wss_outb(chip, CS4231P(STATUS), 0);     /* clear IRQ */
        chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE;
@@ -901,7 +913,8 @@ static void snd_wss_close(struct snd_wss *chip, unsigned int mode)
        }
 
        /* clear IRQ again */
-       snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
+       if (!(chip->hardware & WSS_HW_AD1848_MASK))
+               snd_wss_out(chip, CS4231_IRQ_STATUS, 0);
        wss_outb(chip, CS4231P(STATUS), 0);     /* clear IRQ */
        wss_outb(chip, CS4231P(STATUS), 0);     /* clear IRQ */
        spin_unlock_irqrestore(&chip->reg_lock, flags);
@@ -1022,7 +1035,13 @@ static int snd_wss_capture_prepare(struct snd_pcm_substream *substream)
        chip->c_dma_size = size;
        chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO);
        snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT);
-       count = snd_wss_get_count(chip->image[CS4231_REC_FORMAT], count) - 1;
+       if (chip->hardware & WSS_HW_AD1848_MASK)
+               count = snd_wss_get_count(chip->image[CS4231_PLAYBK_FORMAT],
+                                         count);
+       else
+               count = snd_wss_get_count(chip->image[CS4231_REC_FORMAT],
+                                         count);
+       count--;
        if (chip->single_dma && chip->hardware != WSS_HW_INTERWAVE) {
                snd_wss_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count);
                snd_wss_out(chip, CS4231_PLY_UPR_CNT,
@@ -1054,7 +1073,11 @@ irqreturn_t snd_wss_interrupt(int irq, void *dev_id)
        struct snd_wss *chip = dev_id;
        unsigned char status;
 
-       status = snd_wss_in(chip, CS4231_IRQ_STATUS);
+       if (chip->hardware & WSS_HW_AD1848_MASK)
+               /* pretend it was the only possible irq for AD1848 */
+               status = CS4231_PLAYBACK_IRQ;
+       else
+               status = snd_wss_in(chip, CS4231_IRQ_STATUS);
        if (status & CS4231_TIMER_IRQ) {
                if (chip->timer)
                        snd_timer_interrupt(chip->timer, chip->timer->sticks);
@@ -1086,7 +1109,11 @@ irqreturn_t snd_wss_interrupt(int irq, void *dev_id)
        }
 
        spin_lock(&chip->reg_lock);
-       snd_wss_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0);
+       status = ~CS4231_ALL_IRQS | ~status;
+       if (chip->hardware & WSS_HW_AD1848_MASK)
+               wss_outb(chip, CS4231P(STATUS), 0);
+       else
+               snd_wss_outm(chip, CS4231_IRQ_STATUS, status, 0);
        spin_unlock(&chip->reg_lock);
        return IRQ_HANDLED;
 }
@@ -1118,36 +1145,119 @@ static snd_pcm_uframes_t snd_wss_capture_pointer(struct snd_pcm_substream *subst
 
  */
 
+static int snd_ad1848_probe(struct snd_wss *chip)
+{
+       unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+       unsigned long flags;
+       unsigned char r;
+       unsigned short hardware = 0;
+       int err = 0;
+       int i;
+
+       while (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) {
+               if (time_after(jiffies, timeout))
+                       return -ENODEV;
+               cond_resched();
+       }
+       spin_lock_irqsave(&chip->reg_lock, flags);
+
+       /* set CS423x MODE 1 */
+       snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+
+       snd_wss_dout(chip, CS4231_RIGHT_INPUT, 0x45); /* 0x55 & ~0x10 */
+       r = snd_wss_in(chip, CS4231_RIGHT_INPUT);
+       if (r != 0x45) {
+               /* RMGE always high on AD1847 */
+               if ((r & ~CS4231_ENABLE_MIC_GAIN) != 0x45) {
+                       err = -ENODEV;
+                       goto out;
+               }
+               hardware = WSS_HW_AD1847;
+       } else {
+               snd_wss_dout(chip, CS4231_LEFT_INPUT,  0xaa);
+               r = snd_wss_in(chip, CS4231_LEFT_INPUT);
+               /* L/RMGE always low on AT2320 */
+               if ((r | CS4231_ENABLE_MIC_GAIN) != 0xaa) {
+                       err = -ENODEV;
+                       goto out;
+               }
+       }
+
+       /* clear pending IRQ */
+       wss_inb(chip, CS4231P(STATUS));
+       wss_outb(chip, CS4231P(STATUS), 0);
+       mb();
+
+       if ((chip->hardware & WSS_HW_TYPE_MASK) != WSS_HW_DETECT)
+               goto out;
+
+       if (hardware) {
+               chip->hardware = hardware;
+               goto out;
+       }
+
+       r = snd_wss_in(chip, CS4231_MISC_INFO);
+
+       /* set CS423x MODE 2 */
+       snd_wss_dout(chip, CS4231_MISC_INFO, CS4231_MODE2);
+       for (i = 0; i < 16; i++) {
+               if (snd_wss_in(chip, i) != snd_wss_in(chip, 16 + i)) {
+                       /* we have more than 16 registers: check ID */
+                       if ((r & 0xf) != 0xa)
+                               goto out_mode;
+                       /*
+                        * on CMI8330, CS4231_VERSION is volume control and
+                        * can be set to 0
+                        */
+                       snd_wss_dout(chip, CS4231_VERSION, 0);
+                       r = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
+                       if (!r)
+                               chip->hardware = WSS_HW_CMI8330;
+                       goto out_mode;
+               }
+       }
+       if (r & 0x80)
+               chip->hardware = WSS_HW_CS4248;
+       else
+               chip->hardware = WSS_HW_AD1848;
+out_mode:
+       snd_wss_dout(chip, CS4231_MISC_INFO, 0);
+out:
+       spin_unlock_irqrestore(&chip->reg_lock, flags);
+       return err;
+}
+
 static int snd_wss_probe(struct snd_wss *chip)
 {
        unsigned long flags;
-       int i, id, rev;
+       int i, id, rev, regnum;
        unsigned char *ptr;
        unsigned int hw;
 
-#if 0
-       snd_wss_debug(chip);
-#endif
-       id = 0;
-       for (i = 0; i < 50; i++) {
-               mb();
-               if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
-                       udelay(2000);
-               else {
-                       spin_lock_irqsave(&chip->reg_lock, flags);
-                       snd_wss_out(chip, CS4231_MISC_INFO, CS4231_MODE2);
-                       id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f;
-                       spin_unlock_irqrestore(&chip->reg_lock, flags);
-                       if (id == 0x0a)
-                               break;  /* this is valid value */
-               }
-       }
-       snd_printdd("wss: port = 0x%lx, id = 0x%x\n", chip->port, id);
-       if (id != 0x0a)
-               return -ENODEV; /* no valid device found */
+       id = snd_ad1848_probe(chip);
+       if (id < 0)
+               return id;
 
        hw = chip->hardware;
        if ((hw & WSS_HW_TYPE_MASK) == WSS_HW_DETECT) {
+               for (i = 0; i < 50; i++) {
+                       mb();
+                       if (wss_inb(chip, CS4231P(REGSEL)) & CS4231_INIT)
+                               msleep(2);
+                       else {
+                               spin_lock_irqsave(&chip->reg_lock, flags);
+                               snd_wss_out(chip, CS4231_MISC_INFO,
+                                           CS4231_MODE2);
+                               id = snd_wss_in(chip, CS4231_MISC_INFO) & 0x0f;
+                               spin_unlock_irqrestore(&chip->reg_lock, flags);
+                               if (id == 0x0a)
+                                       break;  /* this is valid value */
+                       }
+               }
+               snd_printdd("wss: port = 0x%lx, id = 0x%x\n", chip->port, id);
+               if (id != 0x0a)
+                       return -ENODEV; /* no valid device found */
+
                rev = snd_wss_in(chip, CS4231_VERSION) & 0xe7;
                snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev);
                if (rev == 0x80) {
@@ -1178,7 +1288,8 @@ static int snd_wss_probe(struct snd_wss *chip)
        mb();
        spin_unlock_irqrestore(&chip->reg_lock, flags);
 
-       chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
+       if (!(chip->hardware & WSS_HW_AD1848_MASK))
+               chip->image[CS4231_MISC_INFO] = CS4231_MODE2;
        switch (chip->hardware) {
        case WSS_HW_INTERWAVE:
                chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3;
@@ -1204,9 +1315,10 @@ static int snd_wss_probe(struct snd_wss *chip)
                        chip->hardware == WSS_HW_INTERWAVE ? 0xc2 : 0x01;
        }
        ptr = (unsigned char *) &chip->image;
+       regnum = (chip->hardware & WSS_HW_AD1848_MASK) ? 16 : 32;
        snd_wss_mce_down(chip);
        spin_lock_irqsave(&chip->reg_lock, flags);
-       for (i = 0; i < 32; i++)        /* ok.. fill all CS4231 registers */
+       for (i = 0; i < regnum; i++)    /* ok.. fill all registers */
                snd_wss_out(chip, i, *ptr++);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        snd_wss_mce_up(chip);
@@ -1340,6 +1452,11 @@ static int snd_wss_playback_open(struct snd_pcm_substream *substream)
 
        runtime->hw = snd_wss_playback;
 
+       /* hardware limitation of older chipsets */
+       if (chip->hardware & WSS_HW_AD1848_MASK)
+               runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+                                        SNDRV_PCM_FMTBIT_S16_BE);
+
        /* hardware bug in InterWave chipset */
        if (chip->hardware == WSS_HW_INTERWAVE && chip->dma1 > 3)
                runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW;
@@ -1378,10 +1495,17 @@ static int snd_wss_capture_open(struct snd_pcm_substream *substream)
 
        runtime->hw = snd_wss_capture;
 
+       /* hardware limitation of older chipsets */
+       if (chip->hardware & WSS_HW_AD1848_MASK)
+               runtime->hw.formats &= ~(SNDRV_PCM_FMTBIT_IMA_ADPCM |
+                                        SNDRV_PCM_FMTBIT_S16_BE);
+
        /* hardware limitation of cheap chips */
        if (chip->hardware == WSS_HW_CS4235 ||
-           chip->hardware == WSS_HW_CS4239)
-               runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE;
+           chip->hardware == WSS_HW_CS4239 ||
+           chip->hardware == WSS_HW_OPTI93X)
+               runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 |
+                                     SNDRV_PCM_FMTBIT_S16_LE;
 
        snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max);
        snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max);
@@ -1422,6 +1546,26 @@ static int snd_wss_capture_close(struct snd_pcm_substream *substream)
        return 0;
 }
 
+static void snd_wss_thinkpad_twiddle(struct snd_wss *chip, int on)
+{
+       int tmp;
+
+       if (!chip->thinkpad_flag)
+               return;
+
+       outb(0x1c, AD1848_THINKPAD_CTL_PORT1);
+       tmp = inb(AD1848_THINKPAD_CTL_PORT2);
+
+       if (on)
+               /* turn it on */
+               tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT;
+       else
+               /* turn it off */
+               tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT;
+
+       outb(tmp, AD1848_THINKPAD_CTL_PORT2);
+}
+
 #ifdef CONFIG_PM
 
 /* lowlevel suspend callback for CS4231 */
@@ -1435,6 +1579,8 @@ static void snd_wss_suspend(struct snd_wss *chip)
        for (reg = 0; reg < 32; reg++)
                chip->image[reg] = snd_wss_in(chip, reg);
        spin_unlock_irqrestore(&chip->reg_lock, flags);
+       if (chip->thinkpad_flag)
+               snd_wss_thinkpad_twiddle(chip, 0);
 }
 
 /* lowlevel resume callback for CS4231 */
@@ -1444,6 +1590,8 @@ static void snd_wss_resume(struct snd_wss *chip)
        unsigned long flags;
        /* int timeout; */
 
+       if (chip->thinkpad_flag)
+               snd_wss_thinkpad_twiddle(chip, 1);
        snd_wss_mce_up(chip);
        spin_lock_irqsave(&chip->reg_lock, flags);
        for (reg = 0; reg < 32; reg++) {
@@ -1541,6 +1689,14 @@ const char *snd_wss_chip_id(struct snd_wss *chip)
                return "AD1845";
        case WSS_HW_OPTI93X:
                return "OPTi 93x";
+       case WSS_HW_AD1847:
+               return "AD1847";
+       case WSS_HW_AD1848:
+               return "AD1848";
+       case WSS_HW_CS4248:
+               return "CS4248";
+       case WSS_HW_CMI8330:
+               return "CMI8330/C3D";
        default:
                return "???";
        }
@@ -1574,6 +1730,10 @@ static int snd_wss_new(struct snd_card *card,
        else
                memcpy(&chip->image, &snd_wss_original_image,
                       sizeof(snd_wss_original_image));
+       if (chip->hardware & WSS_HW_AD1848_MASK) {
+               chip->image[CS4231_PIN_CTRL] = 0;
+               chip->image[CS4231_TEST_INIT] = 0;
+       }
 
        *rchip = chip;
        return 0;
@@ -1601,7 +1761,7 @@ int snd_wss_create(struct snd_card *card,
        chip->dma1 = -1;
        chip->dma2 = -1;
 
-       chip->res_port = request_region(port, 4, "CS4231");
+       chip->res_port = request_region(port, 4, "WSS");
        if (!chip->res_port) {
                snd_printk(KERN_ERR "wss: can't grab port 0x%lx\n", port);
                snd_wss_free(chip);
@@ -1620,20 +1780,20 @@ int snd_wss_create(struct snd_card *card,
        chip->cport = cport;
        if (!(hwshare & WSS_HWSHARE_IRQ))
                if (request_irq(irq, snd_wss_interrupt, IRQF_DISABLED,
-                               "CS4231", (void *) chip)) {
+                               "WSS", (void *) chip)) {
                        snd_printk(KERN_ERR "wss: can't grab IRQ %d\n", irq);
                        snd_wss_free(chip);
                        return -EBUSY;
                }
        chip->irq = irq;
-       if (!(hwshare & WSS_HWSHARE_DMA1) && request_dma(dma1, "CS4231 - 1")) {
+       if (!(hwshare & WSS_HWSHARE_DMA1) && request_dma(dma1, "WSS - 1")) {
                snd_printk(KERN_ERR "wss: can't grab DMA1 %d\n", dma1);
                snd_wss_free(chip);
                return -EBUSY;
        }
        chip->dma1 = dma1;
        if (!(hwshare & WSS_HWSHARE_DMA2) && dma1 != dma2 &&
-             dma2 >= 0 && request_dma(dma2, "CS4231 - 2")) {
+             dma2 >= 0 && request_dma(dma2, "WSS - 2")) {
                snd_printk(KERN_ERR "wss: can't grab DMA2 %d\n", dma2);
                snd_wss_free(chip);
                return -EBUSY;
@@ -1644,6 +1804,12 @@ int snd_wss_create(struct snd_card *card,
        } else
                chip->dma2 = dma2;
 
+       if (hardware == WSS_HW_THINKPAD) {
+               chip->thinkpad_flag = 1;
+               chip->hardware = WSS_HW_DETECT; /* reset */
+               snd_wss_thinkpad_twiddle(chip, 1);
+       }
+
        /* global setup */
        if (snd_wss_probe(chip) < 0) {
                snd_wss_free(chip);
@@ -1703,13 +1869,10 @@ int snd_wss_pcm(struct snd_wss *chip, int device, struct snd_pcm **rpcm)
        struct snd_pcm *pcm;
        int err;
 
-       if ((err = snd_pcm_new(chip->card, "CS4231", device, 1, 1, &pcm)) < 0)
+       err = snd_pcm_new(chip->card, "WSS", device, 1, 1, &pcm);
+       if (err < 0)
                return err;
 
-       spin_lock_init(&chip->reg_lock);
-       mutex_init(&chip->mce_mutex);
-       mutex_init(&chip->open_mutex);
-
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_wss_playback_ops);
        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_wss_capture_ops);
 
@@ -1783,7 +1946,8 @@ static int snd_wss_info_mux(struct snd_kcontrol *kcontrol,
        char **ptexts = texts;
        struct snd_wss *chip = snd_kcontrol_chip(kcontrol);
 
-       snd_assert(chip->card != NULL, return -EINVAL);
+       if (snd_BUG_ON(!chip->card))
+               return -EINVAL;
        uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
        uinfo->count = 2;
        uinfo->value.enumerated.items = 4;
@@ -1957,16 +2121,58 @@ int snd_wss_put_double(struct snd_kcontrol *kcontrol,
        val1 <<= shift_left;
        val2 <<= shift_right;
        spin_lock_irqsave(&chip->reg_lock, flags);
-       val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
-       val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
-       change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg];
-       snd_wss_out(chip, left_reg, val1);
-       snd_wss_out(chip, right_reg, val2);
+       if (left_reg != right_reg) {
+               val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1;
+               val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2;
+               change = val1 != chip->image[left_reg] ||
+                        val2 != chip->image[right_reg];
+               snd_wss_out(chip, left_reg, val1);
+               snd_wss_out(chip, right_reg, val2);
+       } else {
+               mask = (mask << shift_left) | (mask << shift_right);
+               val1 = (chip->image[left_reg] & ~mask) | val1 | val2;
+               change = val1 != chip->image[left_reg];
+               snd_wss_out(chip, left_reg, val1);
+       }
        spin_unlock_irqrestore(&chip->reg_lock, flags);
        return change;
 }
 EXPORT_SYMBOL(snd_wss_put_double);
 
+static const DECLARE_TLV_DB_SCALE(db_scale_6bit, -9450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_5bit_12db_max, -3450, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_rec_gain, 0, 150, 0);
+
+static struct snd_kcontrol_new snd_ad1848_controls[] = {
+WSS_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT,
+          7, 7, 1, 1),
+WSS_DOUBLE_TLV("PCM Playback Volume", 0,
+              CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1,
+              db_scale_6bit),
+WSS_DOUBLE("Aux Playback Switch", 0,
+          CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 0,
+              CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1,
+              db_scale_5bit_12db_max),
+WSS_DOUBLE("Aux Playback Switch", 1,
+          CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1),
+WSS_DOUBLE_TLV("Aux Playback Volume", 1,
+              CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1,
+              db_scale_5bit_12db_max),
+WSS_DOUBLE_TLV("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT,
+               0, 0, 15, 0, db_scale_rec_gain),
+{
+       .name = "Capture Source",
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .info = snd_wss_info_mux,
+       .get = snd_wss_get_mux,
+       .put = snd_wss_put_mux,
+},
+WSS_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0),
+WSS_SINGLE_TLV("Loopback Capture Volume", 0, CS4231_LOOPBACK, 1, 63, 0,
+              db_scale_6bit),
+};
+
 static struct snd_kcontrol_new snd_wss_controls[] = {
 WSS_DOUBLE("PCM Playback Switch", 0,
                CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1),
@@ -2057,7 +2263,8 @@ int snd_wss_mixer(struct snd_wss *chip)
        unsigned int idx;
        int err;
 
-       snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL);
+       if (snd_BUG_ON(!chip || !chip->pcm))
+               return -EINVAL;
 
        card = chip->card;
 
@@ -2071,6 +2278,14 @@ int snd_wss_mixer(struct snd_wss *chip)
                        if (err < 0)
                                return err;
                }
+       else if (chip->hardware & WSS_HW_AD1848_MASK)
+               for (idx = 0; idx < ARRAY_SIZE(snd_ad1848_controls); idx++) {
+                       err = snd_ctl_add(card,
+                                       snd_ctl_new1(&snd_ad1848_controls[idx],
+                                                    chip));
+                       if (err < 0)
+                               return err;
+               }
        else
                for (idx = 0; idx < ARRAY_SIZE(snd_wss_controls); idx++) {
                        err = snd_ctl_add(card,
@@ -2083,6 +2298,13 @@ int snd_wss_mixer(struct snd_wss *chip)
 }
 EXPORT_SYMBOL(snd_wss_mixer);
 
+const struct snd_pcm_ops *snd_wss_get_pcm_ops(int direction)
+{
+       return direction == SNDRV_PCM_STREAM_PLAYBACK ?
+               &snd_wss_playback_ops : &snd_wss_capture_ops;
+}
+EXPORT_SYMBOL(snd_wss_get_pcm_ops);
+
 /*
  *  INIT part
  */