Index: linux-2.6.21gum/sound/pci/ac97/ac97_codec.c
===================================================================
--- linux-2.6.21gum.orig/sound/pci/ac97/ac97_codec.c
+++ linux-2.6.21gum/sound/pci/ac97/ac97_codec.c
@@ -158,7 +158,7 @@ static const struct ac97_codec_id snd_ac
 { 0x4e534300, 0xffffffff, "LM4540,43,45,46,48",	NULL,		NULL }, // only guess --jk
 { 0x4e534331, 0xffffffff, "LM4549",		NULL,		NULL },
 { 0x4e534350, 0xffffffff, "LM4550",		patch_lm4550,  	NULL }, // volume wrap fix 
-{ 0x50534304, 0xffffffff, "UCB1400",		patch_ucb1400,	NULL },
+{ 0x50534304, 0xffffffff, "UCB1400",		patch_ucb1400,	NULL, AC97_HAS_NO_STD_PCM },
 { 0x53494c20, 0xffffffe0, "Si3036,8",		mpatch_si3036,	mpatch_si3036, AC97_MODEM_PATCH },
 { 0x54524102, 0xffffffff, "TR28022",		NULL,		NULL },
 { 0x54524106, 0xffffffff, "TR28026",		NULL,		NULL },
Index: linux-2.6.21gum/sound/pci/ac97/ac97_patch.c
===================================================================
--- linux-2.6.21gum.orig/sound/pci/ac97/ac97_patch.c
+++ linux-2.6.21gum/sound/pci/ac97/ac97_patch.c
@@ -29,6 +29,10 @@
 #include <linux/slab.h>
 #include <linux/mutex.h>
 
+#include <linux/proc_fs.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/control.h>
@@ -406,6 +410,238 @@
 }
 
 /*
+ * UCB1400 codec
+ */
+
+#define AC97_UCB1400_FCSR1     0x6a
+#define AC97_UCB1400_FCSR2     0x6c
+
+static const struct snd_kcontrol_new ucb1400_snd_ac97_controls[] = {
+       AC97_SINGLE("Tone Control - Bass", AC97_UCB1400_FCSR1, 11, 4, 0),
+       AC97_SINGLE("Tone Control - Treble", AC97_UCB1400_FCSR1, 9, 2, 0),
+       AC97_SINGLE("Headphone Playback Switch", AC97_UCB1400_FCSR1, 6, 1, 0),
+       AC97_SINGLE("De-emphasis", AC97_UCB1400_FCSR1, 5, 1, 0),
+       AC97_SINGLE("DC Filter", AC97_UCB1400_FCSR1, 4, 1, 0),
+       AC97_SINGLE("Hi-pass Filter", AC97_UCB1400_FCSR1, 3, 1, 0),
+       AC97_SINGLE("ADC Filter", AC97_UCB1400_FCSR2, 12, 1, 0),
+};
+
+#define NUM_GPIO_LINES 10
+
+static struct proc_dir_entry *proc_gpio_parent;
+static struct proc_dir_entry *proc_gpios[NUM_GPIO_LINES];
+
+typedef struct
+{
+       int                     gpio;
+       char                    name[32];
+       struct snd_ac97         *ac97;
+} gpio_summary_type;
+
+static gpio_summary_type gpio_summaries[NUM_GPIO_LINES] =
+{
+       { 0,    "UCB1400-0-0" },
+       { 1,    "UCB1400-0-1" },
+       { 2,    "UCB1400-0-2" },
+       { 3,    "UCB1400-0-3" },
+       { 4,    "UCB1400-0-4" },
+       { 5,    "UCB1400-0-5" },
+       { 6,    "UCB1400-0-6" },
+       { 7,    "UCB1400-0-7" },
+       { 8,    "UCB1400-0-8" },
+       { 9,    "UCB1400-0-9" }
+};
+
+
+static int proc_ucb1400_ac97_gpio_write(struct file *file, const char __user *buf,
+                        unsigned long count, void *data)
+{
+    char *cur, lbuf[count + 1];
+    gpio_summary_type *summary = data;
+    u32 direction_is_out, operation_is_set;
+    int i = summary->gpio;
+    u16 dir, value;
+
+    if (!capable(CAP_SYS_ADMIN))
+            return -EACCES;
+
+    memset(lbuf, 0, count + 1);
+
+    if (copy_from_user(lbuf, buf, count))
+            return -EFAULT;
+
+    cur = lbuf;
+
+    // Get current values
+    direction_is_out = !!(snd_ac97_read(summary->ac97, 0x5c) & (0x0001 << i));
+    operation_is_set = !!(snd_ac97_read(summary->ac97, 0x5a) & (0x0001 << i));
+    while(1)
+    {
+            // We accept options: {GPIO|AF1|AF2|AF3}, {set|clear}, {in|out}
+            // Anything else is an error
+            while(cur[0] && (isspace(cur[0]) || ispunct(cur[0]))) cur = &(cur[1]);
+
+            if('\0' == cur[0]) break;
+
+            // Ok, so now we're pointing at the start of something
+            switch(cur[0])
+            {
+                    case 'G':
+                            // Check that next is "PIO" -- '\0' will cause safe short-circuit if end of buf
+                            if(!(cur[1] == 'P' && cur[2] == 'I' && cur[3] == 'O')) goto parse_error;
+                            cur = &(cur[4]);
+                            break;
+                    case 's':
+                            if(!(cur[1] == 'e' && cur[2] == 't')) goto parse_error;
+                            operation_is_set = 1;
+                            cur = &(cur[3]);
+                            break;
+                    case 'c':
+                            if(!(cur[1] == 'l' && cur[2] == 'e' && cur[3] == 'a' && cur[4] == 'r')) goto
+parse_error;
+                            operation_is_set = 0;
+                            cur = &(cur[5]);
+                            break;
+                    case 'i':
+                            if(!(cur[1] == 'n')) goto parse_error;
+                            direction_is_out = 0;
+                            cur = &(cur[2]);
+                            break;
+                    case 'o':
+                            if(!(cur[1] == 'u' && cur[2] == 't')) goto parse_error;
+                            direction_is_out = 1;
+                            cur = &(cur[3]);
+                            break;
+                    default: goto parse_error;
+            }
+    }
+    
+    // set/get value
+    dir = snd_ac97_read(summary->ac97, 0x5c);
+    value = snd_ac97_read(summary->ac97, 0x5a);
+    if (direction_is_out)
+    {
+            dir |= 0x0001 << i;
+            if (operation_is_set)
+            {
+                    value |= 0x0001 << i;
+            }
+            else
+            {
+                    value &= ~(0x0001 << i);
+            }
+
+            snd_ac97_write(summary->ac97, 0x5c, dir);
+            snd_ac97_write(summary->ac97, 0x5a, value);
+    }
+    else // direction in
+    {
+            dir &= ~(0x0001 << i);
+            snd_ac97_write(summary->ac97, 0x5c, dir);
+            operation_is_set = snd_ac97_read(summary->ac97, 0x5a) & ~(0x0001 << i);
+    }
+
+#ifdef CONFIG_PROC_GPIO_DEBUG
+    printk(KERN_INFO "Set (%s,%s,%s) via /proc/gpio/%s\n",
+                            "GPIO",
+                            direction_is_out ? "out" : "in",
+                            operation_is_set ? "set" : "clear",
+                            summary->name);
+#endif
+
+    return count;
+
+parse_error:
+    printk(KERN_CRIT "Parse error: Expect \"GPIO|[set|clear]|[in|out] ...\"\n");
+    return -EINVAL;
+}
+
+static int proc_ucb1400_ac97_gpio_read(char *page, char **start, off_t off,
+                    int count, int *eof, void *data)
+{
+    char *p = page;
+    gpio_summary_type *summary = data;
+    int len, i; /*, af;*/
+    i = summary->gpio;
+
+    p += sprintf(p, "%d\t%s\t%s\t%s\n", i,
+                    "GPIO",
+                    (snd_ac97_read(summary->ac97, 0x5c) & (0x0001 << i)) ? "out" : "in",
+                    (snd_ac97_read(summary->ac97, 0x5a) & (0x0001 << i)) ? "set" : "clear");
+    
+    len = (p - page) - off;
+
+    if(len < 0)
+    {
+            len = 0;
+    }
+
+    *eof = (len <= count) ? 1 : 0;
+    *start = page + off;
+
+    return len;
+}
+
+int patch_ucb1400(struct snd_ac97 * ac97)
+{
+       int err, i;
+
+  proc_gpio_parent = NULL;
+  for (proc_gpio_parent = proc_root.subdir; proc_gpio_parent; proc_gpio_parent = proc_gpio_parent->next) {
+    if (( proc_gpio_parent->namelen == 4 ) && ( memcmp(proc_gpio_parent->name, "gpio", 4 ) == 0 ))
+      break;
+  }
+
+  // proc_gpio_parent will be non-NULL if the directory already exists
+
+  if (!proc_gpio_parent) {
+       proc_gpio_parent = proc_mkdir("gpio", NULL);
+  }
+
+       if(!proc_gpio_parent) return 0;
+ 
+       for(i=0; i < NUM_GPIO_LINES; i++)
+       {
+               proc_gpios[i] = create_proc_entry(gpio_summaries[i].name, 0644, proc_gpio_parent);
+               if(proc_gpios[i])
+               {
+                       gpio_summaries[i].ac97 = ac97;
+                       proc_gpios[i]->data = &gpio_summaries[i];
+                       proc_gpios[i]->read_proc = proc_ucb1400_ac97_gpio_read;
+                       proc_gpios[i]->write_proc = proc_ucb1400_ac97_gpio_write;
+               }
+       }
+
+       for(i = 0; i < ARRAY_SIZE(ucb1400_snd_ac97_controls); i++) {
+               if((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&ucb1400_snd_ac97_controls[i], ac97))) < 0)
+                       return err;
+       }
+
+       snd_ac97_write_cache(ac97,  AC97_UCB1400_FCSR1,
+                       (0 << 11) |     // 0 base boost
+                       (0 << 9)  |     // 0 treble boost
+                       (0 << 7)  |     // Mode = flat
+                       (1 << 6)  |     // Headphones enable
+                       (0 << 5)  |     // De-emphasis disabled
+                       (1 << 4)  |     // DC filter enabled
+                       (1 << 3)  |     // Hi-pass filter enabled
+                       (0 << 2)  |     // disable interrupt signalling via GPIO_INT
+                       (1 << 0)        // clear ADC overflow status if set
+               );
+
+       snd_ac97_write_cache(ac97, AC97_UCB1400_FCSR2,
+                       (0 << 15) |     // must be 0
+                       (0 << 13) |     // must be 0
+                       (1 << 12) |     // ADC filter enabled
+                       (0 << 10) |     // must be 0
+                       (0 << 4)  |     // Smart low power mode on neither Codec nor PLL
+                       (0 << 0)        // must be 0
+               );
+
+       return 0;
+}
+
+/*
  * May 2, 2003 Liam Girdwood <liam.girdwood@wolfsonmicro.com>
  *  removed broken wolfson00 patch.
  *  added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717.
@@ -3408,41 +3644,3 @@
 	ac97->res_table = lm4550_restbl;
 	return 0;
 }
-
-/* 
- *  UCB1400 codec (http://www.semiconductors.philips.com/acrobat_download/datasheets/UCB1400-02.pdf)
- */
-static const struct snd_kcontrol_new snd_ac97_controls_ucb1400[] = {
-/* enable/disable headphone driver which allows direct connection to
-   stereo headphone without the use of external DC blocking
-   capacitors */
-AC97_SINGLE("Headphone Driver", 0x6a, 6, 1, 0),
-/* Filter used to compensate the DC offset is added in the ADC to remove idle
-   tones from the audio band. */
-AC97_SINGLE("DC Filter", 0x6a, 4, 1, 0),
-/* Control smart-low-power mode feature. Allows automatic power down
-   of unused blocks in the ADC analog front end and the PLL. */
-AC97_SINGLE("Smart Low Power Mode", 0x6c, 4, 3, 0),
-};
-
-static int patch_ucb1400_specific(struct snd_ac97 * ac97)
-{
-	int idx, err;
-	for (idx = 0; idx < ARRAY_SIZE(snd_ac97_controls_ucb1400); idx++)
-		if ((err = snd_ctl_add(ac97->bus->card, snd_ctl_new1(&snd_ac97_controls_ucb1400[idx], ac97))) < 0)
-			return err;
-	return 0;
-}
-
-static struct snd_ac97_build_ops patch_ucb1400_ops = {
-	.build_specific	= patch_ucb1400_specific,
-};
-
-int patch_ucb1400(struct snd_ac97 * ac97)
-{
-	ac97->build_ops = &patch_ucb1400_ops;
-	/* enable headphone driver and smart low power mode by default */
-	snd_ac97_write(ac97, 0x6a, 0x0050);
-	snd_ac97_write(ac97, 0x6c, 0x0030);
-	return 0;
-}