diff --git a/arch/arm/include/asm/patch.h b/arch/arm/include/asm/patch.h
new file mode 100644
index 0000000..a89749f
--- /dev/null
+++ b/arch/arm/include/asm/patch.h
@@ -0,0 +1,123 @@
+/*
+ *  arch/arm/include/asm/patch.h
+ *
+ *  Copyright (C) 2012, Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Note: this file should not be included by non-asm/.h files
+ */
+#ifndef __ASM_ARM_PATCH_H
+#define __ASM_ARM_PATCH_H
+
+#include <linux/stringify.h>
+
+#ifndef __ASSEMBLY__
+
+extern unsigned __patch_table_begin, __patch_table_end;
+
+struct patch_info {
+	u32	 type;
+	u32	 size;
+	void	*insn_start;
+	void	*insn_end;
+	u32	 patch_data[0];
+};
+
+#define patch_next(p)		((void *)(p) + (p)->size)
+
+#define PATCH_TYPE_MASK		0xffff
+#define PATCH_IMM8		0x0001
+
+#define PATCH_EARLY		0x10000
+
+#define patch_stub(type, code, patch_data, ...)			\
+	__asm__("@ patch stub\n"				\
+		"1:\n"						\
+		code						\
+		"2:\n"						\
+		"	.pushsection .patch.table, \"a\"\n"	\
+		"3:\n"						\
+		"	.long (" __stringify(type) ")\n"	\
+		"	.long (4f-3b)\n"			\
+		"	.long 1b\n"				\
+		"	.long 2b\n"				\
+		patch_data					\
+		"4:\n"						\
+		"	.popsection\n"				\
+		__VA_ARGS__)
+
+#define early_patch_stub(type, code, patch_data, ...)		\
+	__asm__("@ patch stub\n"				\
+		"1:\n"						\
+		"	b	5f\n"				\
+		"2:\n"						\
+		"	.pushsection .patch.table, \"a\"\n"	\
+		"3:\n"						\
+		"	.long (" __stringify(type | PATCH_EARLY) ")\n" \
+		"	.long (4f-3b)\n"			\
+		"	.long 1b\n"				\
+		"	.long 2b\n"				\
+		patch_data					\
+		"4:\n"						\
+		"	.popsection\n"				\
+		"	.pushsection .patch.code, \"ax\"\n"	\
+		"5:\n"						\
+		code						\
+		"	b 2b\n"					\
+		"	.popsection\n"				\
+		__VA_ARGS__)
+
+/* constant used to force encoding */
+#define __IMM8		(0x81 << 24)
+
+/*
+ * patch_imm8() - init-time specialized binary operation (imm8 operand)
+ *		  This effectively does: to = from "insn" sym,
+ *		  where the value of sym is fixed at init-time, and is patched
+ *		  in as an immediate operand.  This value must be
+ *		  representible as an 8-bit quantity with an optional
+ *		  rotation.
+ *
+ *		  The stub code produced by this variant is non-functional
+ *		  prior to patching.  Use early_patch_imm8() if you need the
+ *		  code to be functional early on in the init sequence.
+ */
+#define patch_imm8(from, to, insn, sym)				\
+	patch_stub(PATCH_IMM8,					\
+		   /* code */					\
+		   insn " %0, %1, %2\n",			\
+		   /* patch_data */				\
+		   ".long " __stringify(sym)		  "\n"	\
+		   insn " %0, %1, %2\n",			\
+		   : "=r" (to)					\
+		   : "r" (from), "I" (__IMM8), "m" (sym)	\
+		   : "cc")
+
+/*
+ * early_patch_imm8() - early functional variant of patch_imm8() above.  The
+ *			same restrictions on the constant apply here.  This
+ *			version emits workable (albeit inefficient) code at
+ *			compile-time, and therefore functions even prior to
+ *			patch application.
+ */
+#define early_patch_imm8(from, to, insn, sym)			\
+	early_patch_stub(PATCH_IMM8,				\
+			 /* code */				\
+			 "ldr	%0, =" __stringify(sym) "\n"	\
+			 "ldr	%0, [%0]\n"			\
+			 insn " %0, %1, %0\n",			\
+			 /* patch_data */			\
+			 ".long " __stringify(sym) "\n"		\
+			 insn " %0, %1, %2\n",			\
+			 : "=&r" (to)				\
+			 : "r" (from), "I" (__IMM8), "m" (sym)	\
+			 : "cc")
+
+int patch_kernel(const void *table, unsigned size);
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* __ASM_ARM_PATCH_H */
diff --git a/arch/arm/kernel/module.c b/arch/arm/kernel/module.c
index 1e9be5d..df5e897 100644
--- a/arch/arm/kernel/module.c
+++ b/arch/arm/kernel/module.c
@@ -24,6 +24,7 @@
 #include <asm/sections.h>
 #include <asm/smp_plat.h>
 #include <asm/unwind.h>
+#include <asm/patch.h>
 
 #ifdef CONFIG_XIP_KERNEL
 /*
@@ -321,6 +322,9 @@ int module_finalize(const Elf32_Ehdr *hdr, const Elf_Shdr *sechdrs,
 	if (s)
 		fixup_pv_table((void *)s->sh_addr, s->sh_size);
 #endif
+	s = find_mod_section(hdr, sechdrs, ".patch.table");
+	if (s)
+		patch_kernel((void *)s->sh_addr, s->sh_size);
 	s = find_mod_section(hdr, sechdrs, ".alt.smp.init");
 	if (s && !is_smp())
 #ifdef CONFIG_SMP_ON_UP
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c
index e15d83b..15a7699 100644
--- a/arch/arm/kernel/setup.c
+++ b/arch/arm/kernel/setup.c
@@ -55,6 +55,7 @@
 #include <asm/traps.h>
 #include <asm/unwind.h>
 #include <asm/memblock.h>
+#include <asm/opcodes.h>
 
 #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
 #include "compat.h"
@@ -937,6 +938,178 @@ static int __init meminfo_cmp(const void *_a, const void *_b)
 	return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
 }
 
+static int apply_patch_imm8_arm(const struct patch_info *p)
+{
+	u32 insn, ninsn, op, *insn_ptr = p->insn_start;
+	u32 imm, rot, val;
+	int size = p->insn_end - p->insn_start;
+
+	if (size != 4) {
+		pr_err("patch: bad template size %d\n", size);
+		return -EINVAL;
+	}
+
+	insn = __mem_to_opcode_arm(p->patch_data[1]);
+
+	/* disallow special unconditional instructions
+	 * 1111 xxxx xxxx xxxx xxxx xxxx xxxx xxxx */
+	if ((insn >> 24) == 0xf) {
+		pr_err("patch: unconditional insn %08x\n", insn);
+		return -EINVAL;
+	}
+
+	/* allow only data processing (immediate)
+	 * xxxx 001x xxxx xxxx xxxx xxxx xxxx xxxx */
+	if (((insn >> 25) & 0x3) != 1) {
+		pr_err("patch: unknown insn %08x\n", insn);
+		return -EINVAL;
+	}
+
+	/* extract op code */
+	op = (insn >> 20) & 0x1f;
+
+	/* disallow unsupported 10xxx op codes */
+	if (((op >> 3) & 0x3) == 2) {
+		pr_err("patch: unsupported opcode %08x\n", insn);
+		return -EINVAL;
+	}
+
+	/* disallow Rn == PC and Rd == PC */
+	if (((insn >> 16) & 0xf) == 0xf || ((insn >> 12) & 0xf) == 0xf) {
+		pr_err("patch: unsupported register %08x\n", insn);
+		return -EINVAL;
+	}
+
+	imm = *(u32 *)p->patch_data[0];
+
+	rot = imm ? __ffs(imm) / 2 : 0;
+	val = imm >> (rot * 2);
+	rot = (-rot) & 0xf;
+
+	/* does this fit in 8-bit? */
+	if (val > 0xff) {
+		pr_err("patch: constant overflow %08x\n", imm);
+		return -EINVAL;
+	}
+
+	/* patch in new immediate and rotation */
+	ninsn = (insn & ~0xfff) | (rot << 8) | val;
+
+	*insn_ptr = __opcode_to_mem_arm(ninsn);
+
+	return 0;
+}
+
+static int apply_patch_imm8_thumb(const struct patch_info *p)
+{
+	u32 insn, ninsn, op, *insn_ptr = p->insn_start;
+	u32 imm, rot, val;
+	int size = p->insn_end - p->insn_start;
+	const u32 supported_ops = (BIT(0)  | /* and */
+				   BIT(1)  | /* bic */
+				   BIT(2)  | /* orr/mov */
+				   BIT(3)  | /* orn/mvn */
+				   BIT(4)  | /* eor */
+				   BIT(8)  | /* add */
+				   BIT(10) | /* adc */
+				   BIT(11) | /* sbc */
+				   BIT(12) | /* sub */
+				   BIT(13)); /* rsb */
+
+	if (size != 4) {
+		pr_err("patch: bad template size %d\n", size);
+		return -EINVAL;
+	}
+
+	insn = __mem_to_opcode_thumb32(p->patch_data[1]);
+	if (!__opcode_is_thumb32(insn)) {
+		pr_err("patch: invalid thumb2 insn %08x\n", insn);
+		return -EINVAL;
+	}
+
+	/* allow only data processing (immediate)
+	 * 1111 0x0x xxx0 xxxx 0xxx xxxx xxxx xxxx */
+	if ((insn & 0xfa008000) != 0xf0000000) {
+		pr_err("patch: unknown insn %08x\n", insn);
+		return -EINVAL;
+	}
+
+	/* disallow Rn == PC and Rd == PC */
+	if (((insn >> 8) & 0xf) == 0xf || ((insn >> 16) & 0xf) == 0xf) {
+		pr_err("patch: unsupported register %08x\n", insn);
+		return -EINVAL;
+	}
+
+	/* extract op code */
+	op = (insn >> 21) & 0xf;
+
+	/* disallow unsupported opcodes */
+	if ((supported_ops & BIT(op)) == 0) {
+		pr_err("patch: unsupported opcode %x\n", op);
+		return -EINVAL;
+	}
+
+	imm = *(u32 *)p->patch_data[0];
+
+	if (imm <= 0xff) {
+		rot = 0;
+		val = imm;
+	} else {
+		rot = 32 - fls(imm); /* clz */
+		if (imm & ~(0xff000000 >> rot)) {
+			pr_err("patch: constant overflow %08x\n", imm);
+			return -EINVAL;
+		}
+		val  = (imm >> (24 - rot)) & 0x7f;
+		rot += 8; /* encoded i:imm3:a */
+
+		/* pack least-sig rot bit into most-sig val bit */
+		val |= (rot & 1) << 7;
+		rot >>= 1;
+	}
+
+	ninsn  = insn & ~(BIT(26) | 0x7 << 12 | 0xff);
+	ninsn |= (rot >> 3) << 26;	/* field "i" */
+	ninsn |= (rot & 0x7) << 12;	/* field "imm3" */
+	ninsn |= val;
+
+	*insn_ptr = __opcode_to_mem_thumb32(ninsn);
+
+	return 0;
+}
+
+int patch_kernel(const void *table, unsigned size)
+{
+	const struct patch_info *p = table, *end = (table + size);
+	bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);
+
+	for (p = table; p < end; p = patch_next(p)) {
+		int type = p->type & PATCH_TYPE_MASK;
+		int ret;
+
+		if (type == PATCH_IMM8) {
+			ret = (thumb2 ? apply_patch_imm8_thumb(p) :
+					apply_patch_imm8_arm(p));
+		} else {
+			pr_err("invalid patch type %d\n", type);
+			ret = -EINVAL;
+		}
+
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+static void __init init_patch_kernel(void)
+{
+	const void *start = &__patch_table_begin;
+	const void *end   = &__patch_table_end;
+
+	BUG_ON(patch_kernel(start, end - start));
+	flush_icache_range(init_mm.start_code, init_mm.end_code);
+}
+
 void __init setup_arch(char **cmdline_p)
 {
 	struct machine_desc *mdesc;
@@ -998,6 +1171,8 @@ void __init setup_arch(char **cmdline_p)
 
 	if (mdesc->init_early)
 		mdesc->init_early();
+
+	init_patch_kernel();
 }
 
 
diff --git a/arch/arm/kernel/vmlinux.lds.S b/arch/arm/kernel/vmlinux.lds.S
index 36ff15b..bacb275 100644
--- a/arch/arm/kernel/vmlinux.lds.S
+++ b/arch/arm/kernel/vmlinux.lds.S
@@ -167,6 +167,16 @@ SECTIONS
 		*(.pv_table)
 		__pv_table_end = .;
 	}
+	.init.patch_table : {
+		__patch_table_begin = .;
+		*(.patch.table)
+		__patch_table_end = .;
+	}
+	.init.patch_code : {
+		__patch_code_begin = .;
+		*(.patch.code)
+		__patch_code_end = .;
+	}
 	.init.data : {
 #ifndef CONFIG_XIP_KERNEL
 		INIT_DATA
