diff -druN linux-2.6.32.50-libre2/arch/arm/mach-davinci/board-dm646x-evm.c linux-2.6.32.51-libre2/arch/arm/mach-davinci/board-dm646x-evm.c
--- linux-2.6.32.50-libre2/arch/arm/mach-davinci/board-dm646x-evm.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/arch/arm/mach-davinci/board-dm646x-evm.c	2011-12-21 16:05:40.000000000 -0500
@@ -502,7 +502,7 @@
 	int val;
 	u32 value;
 
-	if (!vpif_vsclkdis_reg || !cpld_client)
+	if (!vpif_vidclkctl_reg || !cpld_client)
 		return -ENXIO;
 
 	val = i2c_smbus_read_byte(cpld_client);
@@ -510,7 +510,7 @@
 		return val;
 
 	spin_lock_irqsave(&vpif_reg_lock, flags);
-	value = __raw_readl(vpif_vsclkdis_reg);
+	value = __raw_readl(vpif_vidclkctl_reg);
 	if (mux_mode) {
 		val &= VPIF_INPUT_TWO_CHANNEL;
 		value |= VIDCH1CLK;
@@ -518,7 +518,7 @@
 		val |= VPIF_INPUT_ONE_CHANNEL;
 		value &= ~VIDCH1CLK;
 	}
-	__raw_writel(value, vpif_vsclkdis_reg);
+	__raw_writel(value, vpif_vidclkctl_reg);
 	spin_unlock_irqrestore(&vpif_reg_lock, flags);
 
 	err = i2c_smbus_write_byte(cpld_client, val);
diff -druN linux-2.6.32.50-libre2/arch/x86/oprofile/backtrace.c linux-2.6.32.51-libre2/arch/x86/oprofile/backtrace.c
--- linux-2.6.32.50-libre2/arch/x86/oprofile/backtrace.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/arch/x86/oprofile/backtrace.c	2011-12-21 16:05:40.000000000 -0500
@@ -11,6 +11,8 @@
 #include <linux/oprofile.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
+#include <linux/highmem.h>
+
 #include <asm/ptrace.h>
 #include <asm/uaccess.h>
 #include <asm/stacktrace.h>
@@ -47,6 +49,42 @@
 	.address = backtrace_address,
 };
 
+/* from arch/x86/kernel/cpu/perf_event.c: */
+
+/*
+ * best effort, GUP based copy_from_user() that assumes IRQ or NMI context
+ */
+static unsigned long
+copy_from_user_nmi(void *to, const void __user *from, unsigned long n)
+{
+	unsigned long offset, addr = (unsigned long)from;
+	unsigned long size, len = 0;
+	struct page *page;
+	void *map;
+	int ret;
+
+	do {
+		ret = __get_user_pages_fast(addr, 1, 0, &page);
+		if (!ret)
+			break;
+
+		offset = addr & (PAGE_SIZE - 1);
+		size = min(PAGE_SIZE - offset, n - len);
+
+		map = kmap_atomic(page, KM_USER0);
+		memcpy(to, map+offset, size);
+		kunmap_atomic(map, KM_USER0);
+		put_page(page);
+
+		len  += size;
+		to   += size;
+		addr += size;
+
+	} while (len < n);
+
+	return len;
+}
+
 struct frame_head {
 	struct frame_head *bp;
 	unsigned long ret;
@@ -54,12 +92,12 @@
 
 static struct frame_head *dump_user_backtrace(struct frame_head *head)
 {
+	/* Also check accessibility of one struct frame_head beyond: */
 	struct frame_head bufhead[2];
+	unsigned long bytes;
 
-	/* Also check accessibility of one struct frame_head beyond */
-	if (!access_ok(VERIFY_READ, head, sizeof(bufhead)))
-		return NULL;
-	if (__copy_from_user_inatomic(bufhead, head, sizeof(bufhead)))
+	bytes = copy_from_user_nmi(bufhead, head, sizeof(bufhead));
+	if (bytes != sizeof(bufhead))
 		return NULL;
 
 	oprofile_add_trace(bufhead[0].ret);
diff -druN linux-2.6.32.50-libre2/arch/x86/oprofile/nmi_int.c linux-2.6.32.51-libre2/arch/x86/oprofile/nmi_int.c
--- linux-2.6.32.50-libre2/arch/x86/oprofile/nmi_int.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/arch/x86/oprofile/nmi_int.c	2011-12-21 16:05:40.000000000 -0500
@@ -750,12 +750,12 @@
 
 void op_nmi_exit(void)
 {
-	if (using_nmi) {
-		exit_sysfs();
+	if (!using_nmi)
+		return;
+	exit_sysfs();
 #ifdef CONFIG_SMP
-		unregister_cpu_notifier(&oprofile_cpu_nb);
+	unregister_cpu_notifier(&oprofile_cpu_nb);
 #endif
-	}
 	if (model->exit)
 		model->exit();
 }
diff -druN linux-2.6.32.50-libre2/drivers/oprofile/buffer_sync.c linux-2.6.32.51-libre2/drivers/oprofile/buffer_sync.c
--- linux-2.6.32.50-libre2/drivers/oprofile/buffer_sync.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/drivers/oprofile/buffer_sync.c	2011-12-21 16:05:40.000000000 -0500
@@ -140,6 +140,13 @@
 	.notifier_call = module_load_notify,
 };
 
+static void free_all_tasks(void)
+{
+	/* make sure we don't leak task structs */
+	process_task_mortuary();
+	process_task_mortuary();
+}
+
 int sync_start(void)
 {
 	int err;
@@ -147,8 +154,6 @@
 	if (!zalloc_cpumask_var(&marked_cpus, GFP_KERNEL))
 		return -ENOMEM;
 
-	mutex_lock(&buffer_mutex);
-
 	err = task_handoff_register(&task_free_nb);
 	if (err)
 		goto out1;
@@ -165,7 +170,6 @@
 	start_cpu_work();
 
 out:
-	mutex_unlock(&buffer_mutex);
 	return err;
 out4:
 	profile_event_unregister(PROFILE_MUNMAP, &munmap_nb);
@@ -173,6 +177,7 @@
 	profile_event_unregister(PROFILE_TASK_EXIT, &task_exit_nb);
 out2:
 	task_handoff_unregister(&task_free_nb);
+	free_all_tasks();
 out1:
 	free_cpumask_var(marked_cpus);
 	goto out;
@@ -181,20 +186,16 @@
 
 void sync_stop(void)
 {
-	/* flush buffers */
-	mutex_lock(&buffer_mutex);
 	end_cpu_work();
 	unregister_module_notifier(&module_load_nb);
 	profile_event_unregister(PROFILE_MUNMAP, &munmap_nb);
 	profile_event_unregister(PROFILE_TASK_EXIT, &task_exit_nb);
 	task_handoff_unregister(&task_free_nb);
-	mutex_unlock(&buffer_mutex);
-	flush_scheduled_work();
+	barrier();			/* do all of the above first */
 
-	/* make sure we don't leak task structs */
-	process_task_mortuary();
-	process_task_mortuary();
+	flush_scheduled_work();
 
+	free_all_tasks();
 	free_cpumask_var(marked_cpus);
 }
 
diff -druN linux-2.6.32.50-libre2/drivers/usb/class/cdc-acm.c linux-2.6.32.51-libre2/drivers/usb/class/cdc-acm.c
--- linux-2.6.32.50-libre2/drivers/usb/class/cdc-acm.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/drivers/usb/class/cdc-acm.c	2011-12-21 16:05:40.000000000 -0500
@@ -1528,6 +1528,16 @@
 	},
 	{ USB_DEVICE(0x22b8, 0x6425), /* Motorola MOTOMAGX phones */
 	},
+	/* Motorola H24 HSPA module: */
+	{ USB_DEVICE(0x22b8, 0x2d91) }, /* modem                                */
+	{ USB_DEVICE(0x22b8, 0x2d92) }, /* modem           + diagnostics        */
+	{ USB_DEVICE(0x22b8, 0x2d93) }, /* modem + AT port                      */
+	{ USB_DEVICE(0x22b8, 0x2d95) }, /* modem + AT port + diagnostics        */
+	{ USB_DEVICE(0x22b8, 0x2d96) }, /* modem                         + NMEA */
+	{ USB_DEVICE(0x22b8, 0x2d97) }, /* modem           + diagnostics + NMEA */
+	{ USB_DEVICE(0x22b8, 0x2d99) }, /* modem + AT port               + NMEA */
+	{ USB_DEVICE(0x22b8, 0x2d9a) }, /* modem + AT port + diagnostics + NMEA */
+
 	{ USB_DEVICE(0x0572, 0x1329), /* Hummingbird huc56s (Conexant) */
 	.driver_info = NO_UNION_NORMAL, /* union descriptor misplaced on
 					   data interface instead of
diff -druN linux-2.6.32.50-libre2/fs/ext4/inode.c linux-2.6.32.51-libre2/fs/ext4/inode.c
--- linux-2.6.32.50-libre2/fs/ext4/inode.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/fs/ext4/inode.c	2011-12-21 16:05:40.000000000 -0500
@@ -3228,7 +3228,7 @@
 	 */
 
 	new_i_size = pos + copied;
-	if (new_i_size > EXT4_I(inode)->i_disksize) {
+	if (copied && new_i_size > EXT4_I(inode)->i_disksize) {
 		if (ext4_da_should_update_i_disksize(page, end)) {
 			down_write(&EXT4_I(inode)->i_data_sem);
 			if (new_i_size > EXT4_I(inode)->i_disksize) {
diff -druN linux-2.6.32.50-libre2/fs/hfs/btree.c linux-2.6.32.51-libre2/fs/hfs/btree.c
--- linux-2.6.32.50-libre2/fs/hfs/btree.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/fs/hfs/btree.c	2011-12-21 16:05:40.000000000 -0500
@@ -45,11 +45,26 @@
 	case HFS_EXT_CNID:
 		hfs_inode_read_fork(tree->inode, mdb->drXTExtRec, mdb->drXTFlSize,
 				    mdb->drXTFlSize, be32_to_cpu(mdb->drXTClpSiz));
+		if (HFS_I(tree->inode)->alloc_blocks >
+					HFS_I(tree->inode)->first_blocks) {
+			printk(KERN_ERR "hfs: invalid btree extent records\n");
+			unlock_new_inode(tree->inode);
+			goto free_inode;
+		}
+
 		tree->inode->i_mapping->a_ops = &hfs_btree_aops;
 		break;
 	case HFS_CAT_CNID:
 		hfs_inode_read_fork(tree->inode, mdb->drCTExtRec, mdb->drCTFlSize,
 				    mdb->drCTFlSize, be32_to_cpu(mdb->drCTClpSiz));
+
+		if (!HFS_I(tree->inode)->first_blocks) {
+			printk(KERN_ERR "hfs: invalid btree extent records "
+								"(0 size).\n");
+			unlock_new_inode(tree->inode);
+			goto free_inode;
+		}
+
 		tree->inode->i_mapping->a_ops = &hfs_btree_aops;
 		break;
 	default:
@@ -58,11 +73,6 @@
 	}
 	unlock_new_inode(tree->inode);
 
-	if (!HFS_I(tree->inode)->first_blocks) {
-		printk(KERN_ERR "hfs: invalid btree extent records (0 size).\n");
-		goto free_inode;
-	}
-
 	mapping = tree->inode->i_mapping;
 	page = read_mapping_page(mapping, 0, NULL);
 	if (IS_ERR(page))
diff -druN linux-2.6.32.50-libre2/fs/jbd/journal.c linux-2.6.32.51-libre2/fs/jbd/journal.c
--- linux-2.6.32.50-libre2/fs/jbd/journal.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/fs/jbd/journal.c	2011-12-21 16:05:40.000000000 -0500
@@ -1070,6 +1070,14 @@
 		goto out;
 	}
 
+	if (be32_to_cpu(sb->s_first) == 0 ||
+	    be32_to_cpu(sb->s_first) >= journal->j_maxlen) {
+		printk(KERN_WARNING
+			"JBD: Invalid start block of journal: %u\n",
+			be32_to_cpu(sb->s_first));
+		goto out;
+	}
+
 	return 0;
 
 out:
diff -druN linux-2.6.32.50-libre2/fs/jbd2/journal.c linux-2.6.32.51-libre2/fs/jbd2/journal.c
--- linux-2.6.32.50-libre2/fs/jbd2/journal.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/fs/jbd2/journal.c	2011-12-21 16:05:40.000000000 -0500
@@ -1183,6 +1183,14 @@
 		goto out;
 	}
 
+	if (be32_to_cpu(sb->s_first) == 0 ||
+	    be32_to_cpu(sb->s_first) >= journal->j_maxlen) {
+		printk(KERN_WARNING
+			"JBD2: Invalid start block of journal: %u\n",
+			be32_to_cpu(sb->s_first));
+		goto out;
+	}
+
 	return 0;
 
 out:
diff -druN linux-2.6.32.50-libre2/include/linux/log2.h linux-2.6.32.51-libre2/include/linux/log2.h
--- linux-2.6.32.50-libre2/include/linux/log2.h	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/include/linux/log2.h	2011-12-21 16:05:40.000000000 -0500
@@ -185,7 +185,6 @@
 #define rounddown_pow_of_two(n)			\
 (						\
 	__builtin_constant_p(n) ? (		\
-		(n == 1) ? 0 :			\
 		(1UL << ilog2(n))) :		\
 	__rounddown_pow_of_two(n)		\
  )
diff -druN linux-2.6.32.50-libre2/kernel/taskstats.c linux-2.6.32.51-libre2/kernel/taskstats.c
--- linux-2.6.32.50-libre2/kernel/taskstats.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/kernel/taskstats.c	2011-12-21 16:05:40.000000000 -0500
@@ -592,6 +592,7 @@
 	.cmd		= TASKSTATS_CMD_GET,
 	.doit		= taskstats_user_cmd,
 	.policy		= taskstats_cmd_get_policy,
+	.flags		= GENL_ADMIN_PERM,
 };
 
 static struct genl_ops cgroupstats_ops = {
diff -druN linux-2.6.32.50-libre2/Makefile linux-2.6.32.51-libre2/Makefile
--- linux-2.6.32.50-libre2/Makefile	2011-12-10 15:55:23.000000000 -0500
+++ linux-2.6.32.51-libre2/Makefile	2011-12-21 22:40:43.000000000 -0500
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
 SUBLEVEL = 32
-EXTRAVERSION = .50-libre2
+EXTRAVERSION = .51-libre2
 NAME = Man-Eating Seals of Antiquity
 
 # *DOCUMENTATION*
diff -druN linux-2.6.32.50-libre2/mm/percpu.c linux-2.6.32.51-libre2/mm/percpu.c
--- linux-2.6.32.50-libre2/mm/percpu.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/mm/percpu.c	2011-12-21 16:05:40.000000000 -0500
@@ -110,9 +110,9 @@
 static int pcpu_nr_slots __read_mostly;
 static size_t pcpu_chunk_struct_size __read_mostly;
 
-/* cpus with the lowest and highest unit numbers */
-static unsigned int pcpu_first_unit_cpu __read_mostly;
-static unsigned int pcpu_last_unit_cpu __read_mostly;
+/* cpus with the lowest and highest unit addresses */
+static unsigned int pcpu_low_unit_cpu __read_mostly;
+static unsigned int pcpu_high_unit_cpu __read_mostly;
 
 /* the address of the first chunk which starts with the kernel static area */
 void *pcpu_base_addr __read_mostly;
@@ -746,8 +746,8 @@
 				 int page_start, int page_end)
 {
 	flush_cache_vunmap(
-		pcpu_chunk_addr(chunk, pcpu_first_unit_cpu, page_start),
-		pcpu_chunk_addr(chunk, pcpu_last_unit_cpu, page_end));
+		pcpu_chunk_addr(chunk, pcpu_low_unit_cpu, page_start),
+		pcpu_chunk_addr(chunk, pcpu_high_unit_cpu, page_end));
 }
 
 static void __pcpu_unmap_pages(unsigned long addr, int nr_pages)
@@ -809,8 +809,8 @@
 				      int page_start, int page_end)
 {
 	flush_tlb_kernel_range(
-		pcpu_chunk_addr(chunk, pcpu_first_unit_cpu, page_start),
-		pcpu_chunk_addr(chunk, pcpu_last_unit_cpu, page_end));
+		pcpu_chunk_addr(chunk, pcpu_low_unit_cpu, page_start),
+		pcpu_chunk_addr(chunk, pcpu_high_unit_cpu, page_end));
 }
 
 static int __pcpu_map_pages(unsigned long addr, struct page **pages,
@@ -887,8 +887,8 @@
 				int page_start, int page_end)
 {
 	flush_cache_vmap(
-		pcpu_chunk_addr(chunk, pcpu_first_unit_cpu, page_start),
-		pcpu_chunk_addr(chunk, pcpu_last_unit_cpu, page_end));
+		pcpu_chunk_addr(chunk, pcpu_low_unit_cpu, page_start),
+		pcpu_chunk_addr(chunk, pcpu_high_unit_cpu, page_end));
 }
 
 /**
@@ -1680,7 +1680,9 @@
 
 	for (cpu = 0; cpu < nr_cpu_ids; cpu++)
 		unit_map[cpu] = UINT_MAX;
-	pcpu_first_unit_cpu = NR_CPUS;
+
+	pcpu_low_unit_cpu = NR_CPUS;
+	pcpu_high_unit_cpu = NR_CPUS;
 
 	for (group = 0, unit = 0; group < ai->nr_groups; group++, unit += i) {
 		const struct pcpu_group_info *gi = &ai->groups[group];
@@ -1700,9 +1702,13 @@
 			unit_map[cpu] = unit + i;
 			unit_off[cpu] = gi->base_offset + i * ai->unit_size;
 
-			if (pcpu_first_unit_cpu == NR_CPUS)
-				pcpu_first_unit_cpu = cpu;
-			pcpu_last_unit_cpu = cpu;
+			/* determine low/high unit_cpu */
+			if (pcpu_low_unit_cpu == NR_CPUS ||
+			    unit_off[cpu] < unit_off[pcpu_low_unit_cpu])
+				pcpu_low_unit_cpu = cpu;
+			if (pcpu_high_unit_cpu == NR_CPUS ||
+			    unit_off[cpu] > unit_off[pcpu_high_unit_cpu])
+				pcpu_high_unit_cpu = cpu;
 		}
 	}
 	pcpu_nr_units = unit;
diff -druN linux-2.6.32.50-libre2/mm/util.c linux-2.6.32.51-libre2/mm/util.c
--- linux-2.6.32.50-libre2/mm/util.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/mm/util.c	2011-12-21 16:05:40.000000000 -0500
@@ -233,6 +233,19 @@
 }
 #endif
 
+/*
+ * Like get_user_pages_fast() except its IRQ-safe in that it won't fall
+ * back to the regular GUP.
+ * If the architecture not support this fucntion, simply return with no
+ * page pinned
+ */
+int __attribute__((weak)) __get_user_pages_fast(unsigned long start,
+				 int nr_pages, int write, struct page **pages)
+{
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__get_user_pages_fast);
+
 /**
  * get_user_pages_fast() - pin user pages in memory
  * @start:	starting user address
diff -druN linux-2.6.32.50-libre2/net/xfrm/xfrm_algo.c linux-2.6.32.51-libre2/net/xfrm/xfrm_algo.c
--- linux-2.6.32.50-libre2/net/xfrm/xfrm_algo.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/net/xfrm/xfrm_algo.c	2011-12-21 16:05:40.000000000 -0500
@@ -411,8 +411,8 @@
 	.desc = {
 		.sadb_alg_id = SADB_X_EALG_AESCTR,
 		.sadb_alg_ivlen	= 8,
-		.sadb_alg_minbits = 128,
-		.sadb_alg_maxbits = 256
+		.sadb_alg_minbits = 160,
+		.sadb_alg_maxbits = 288
 	}
 },
 };
diff -druN linux-2.6.32.50-libre2/sound/pci/hda/patch_realtek.c linux-2.6.32.51-libre2/sound/pci/hda/patch_realtek.c
--- linux-2.6.32.50-libre2/sound/pci/hda/patch_realtek.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/sound/pci/hda/patch_realtek.c	2011-12-21 16:05:40.000000000 -0500
@@ -432,6 +432,8 @@
 	imux = &spec->input_mux[mux_idx];
 	if (!imux->num_items && mux_idx > 0)
 		imux = &spec->input_mux[0];
+	if (!imux->num_items)
+		return 0;
 
 	type = get_wcaps_type(get_wcaps(codec, nid));
 	if (type == AC_WID_AUD_MIX) {
diff -druN linux-2.6.32.50-libre2/sound/pci/sis7019.c linux-2.6.32.51-libre2/sound/pci/sis7019.c
--- linux-2.6.32.50-libre2/sound/pci/sis7019.c	2011-12-09 12:24:23.000000000 -0500
+++ linux-2.6.32.51-libre2/sound/pci/sis7019.c	2011-12-21 16:05:40.000000000 -0500
@@ -40,6 +40,7 @@
 static int index = SNDRV_DEFAULT_IDX1;	/* Index 0-MAX */
 static char *id = SNDRV_DEFAULT_STR1;	/* ID for this card */
 static int enable = 1;
+static int codecs = 1;
 
 module_param(index, int, 0444);
 MODULE_PARM_DESC(index, "Index value for SiS7019 Audio Accelerator.");
@@ -47,6 +48,8 @@
 MODULE_PARM_DESC(id, "ID string for SiS7019 Audio Accelerator.");
 module_param(enable, bool, 0444);
 MODULE_PARM_DESC(enable, "Enable SiS7019 Audio Accelerator.");
+module_param(codecs, int, 0444);
+MODULE_PARM_DESC(codecs, "Set bit to indicate that codec number is expected to be present (default 1)");
 
 static struct pci_device_id snd_sis7019_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_SI, 0x7019) },
@@ -139,6 +142,9 @@
 	dma_addr_t silence_dma_addr;
 };
 
+/* These values are also used by the module param 'codecs' to indicate
+ * which codecs should be present.
+ */
 #define SIS_PRIMARY_CODEC_PRESENT	0x0001
 #define SIS_SECONDARY_CODEC_PRESENT	0x0002
 #define SIS_TERTIARY_CODEC_PRESENT	0x0004
@@ -1075,6 +1081,7 @@
 {
 	unsigned long io = sis->ioport;
 	void __iomem *ioaddr = sis->ioaddr;
+	unsigned long timeout;
 	u16 status;
 	int count;
 	int i;
@@ -1101,21 +1108,45 @@
 	while ((inw(io + SIS_AC97_STATUS) & SIS_AC97_STATUS_BUSY) && --count)
 		udelay(1);
 
+	/* Command complete, we can let go of the semaphore now.
+	 */
+	outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA);
+	if (!count)
+		return -EIO;
+
 	/* Now that we've finished the reset, find out what's attached.
+	 * There are some codec/board combinations that take an extremely
+	 * long time to come up. 350+ ms has been observed in the field,
+	 * so we'll give them up to 500ms.
 	 */
-	status = inl(io + SIS_AC97_STATUS);
-	if (status & SIS_AC97_STATUS_CODEC_READY)
-		sis->codecs_present |= SIS_PRIMARY_CODEC_PRESENT;
-	if (status & SIS_AC97_STATUS_CODEC2_READY)
-		sis->codecs_present |= SIS_SECONDARY_CODEC_PRESENT;
-	if (status & SIS_AC97_STATUS_CODEC3_READY)
-		sis->codecs_present |= SIS_TERTIARY_CODEC_PRESENT;
+	sis->codecs_present = 0;
+	timeout = msecs_to_jiffies(500) + jiffies;
+	while (time_before_eq(jiffies, timeout)) {
+		status = inl(io + SIS_AC97_STATUS);
+		if (status & SIS_AC97_STATUS_CODEC_READY)
+			sis->codecs_present |= SIS_PRIMARY_CODEC_PRESENT;
+		if (status & SIS_AC97_STATUS_CODEC2_READY)
+			sis->codecs_present |= SIS_SECONDARY_CODEC_PRESENT;
+		if (status & SIS_AC97_STATUS_CODEC3_READY)
+			sis->codecs_present |= SIS_TERTIARY_CODEC_PRESENT;
 
-	/* All done, let go of the semaphore, and check for errors
+		if (sis->codecs_present == codecs)
+			break;
+
+		msleep(1);
+	}
+
+	/* All done, check for errors.
 	 */
-	outl(SIS_AC97_SEMA_RELEASE, io + SIS_AC97_SEMA);
-	if (!sis->codecs_present || !count)
+	if (!sis->codecs_present) {
+		printk(KERN_ERR "sis7019: could not find any codecs\n");
 		return -EIO;
+	}
+
+	if (sis->codecs_present != codecs) {
+		printk(KERN_WARNING "sis7019: missing codecs, found %0x, expected %0x\n",
+		       sis->codecs_present, codecs);
+	}
 
 	/* Let the hardware know that the audio driver is alive,
 	 * and enable PCM slots on the AC-link for L/R playback (3 & 4) and
@@ -1387,6 +1418,17 @@
 	if (!enable)
 		goto error_out;
 
+	/* The user can specify which codecs should be present so that we
+	 * can wait for them to show up if they are slow to recover from
+	 * the AC97 cold reset. We default to a single codec, the primary.
+	 *
+	 * We assume that SIS_PRIMARY_*_PRESENT matches bits 0-2.
+	 */
+	codecs &= SIS_PRIMARY_CODEC_PRESENT | SIS_SECONDARY_CODEC_PRESENT |
+		  SIS_TERTIARY_CODEC_PRESENT;
+	if (!codecs)
+		codecs = SIS_PRIMARY_CODEC_PRESENT;
+
 	rc = snd_card_create(index, id, THIS_MODULE, sizeof(*sis), &card);
 	if (rc < 0)
 		goto error_out;
