Merge Broadcom Linux 4.1-1.1

Change-Id: Ic81528f3de07cd4e3007bfe2af0ebd7badf50ebc
diff --git a/Makefile b/Makefile
index ba52b02..7141c89 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 VERSION = 4
 PATCHLEVEL = 1
 SUBLEVEL = 20
-EXTRAVERSION = -1.1
+EXTRAVERSION = -1.1-gfiber0
 NAME = Series 4800
 
 # *DOCUMENTATION*
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 003431d..8515bd2 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2126,3 +2126,5 @@
 source "lib/Kconfig"
 
 source "arch/arm/kvm/Kconfig"
+
+source "arch/arm/bruno/Kconfig"
diff --git a/arch/arm/bruno/Kconfig b/arch/arm/bruno/Kconfig
new file mode 100644
index 0000000..a691cba
--- /dev/null
+++ b/arch/arm/bruno/Kconfig
@@ -0,0 +1,5 @@
+config BRUNO
+	bool "BRUNO Platform"
+	default y
+	help
+	  This enables support for BRUNO platform.
diff --git a/arch/arm/configs/bruno_gfhd254_defconfig b/arch/arm/configs/bruno_gfhd254_defconfig
new file mode 100644
index 0000000..eb9f493
--- /dev/null
+++ b/arch/arm/configs/bruno_gfhd254_defconfig
@@ -0,0 +1,283 @@
+CONFIG_CROSS_COMPILE="arm-linux-gnueabihf-"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_FHANDLE=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_IRQ_TIME_ACCOUNTING=y
+CONFIG_LOG_BUF_SHIFT=16
+CONFIG_CGROUPS=y
+CONFIG_RELAY=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_PRINTK_PERSIST=y
+CONFIG_EMBEDDED=y
+CONFIG_PERF_EVENTS=y
+CONFIG_SLAB=y
+CONFIG_MODULES=y
+CONFIG_MODULE_FORCE_LOAD=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BRCMSTB=y
+CONFIG_BCM7439B0=y
+CONFIG_ARM_LPAE=y
+CONFIG_PCI=y
+CONFIG_PCI_MSI=y
+CONFIG_SMP=y
+CONFIG_ARM_PSCI=y
+CONFIG_AEABI=y
+CONFIG_HIGHMEM=y
+CONFIG_HIGHPTE=y
+CONFIG_SPARSEMEM_MANUAL=y
+CONFIG_CMA=y
+CONFIG_ARM_APPENDED_DTB=y
+CONFIG_ARM_ATAG_DTB_COMPAT=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_IDLE=y
+CONFIG_VFP=y
+CONFIG_NEON=y
+CONFIG_PM_RUNTIME=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+# CONFIG_INET_XFRM_MODE_TRANSPORT is not set
+# CONFIG_INET_XFRM_MODE_TUNNEL is not set
+# CONFIG_INET_XFRM_MODE_BEET is not set
+# CONFIG_INET_LRO is not set
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BIC=y
+CONFIG_TCP_CONG_WESTWOOD=y
+CONFIG_TCP_CONG_HTCP=y
+CONFIG_TCP_CONG_HSTCP=y
+CONFIG_TCP_CONG_HYBLA=y
+CONFIG_TCP_CONG_SCALABLE=y
+CONFIG_TCP_CONG_LP=y
+CONFIG_TCP_CONG_VENO=y
+CONFIG_TCP_CONG_YEAH=y
+CONFIG_TCP_CONG_ILLINOIS=y
+CONFIG_IPV6=y
+CONFIG_BRIDGE=y
+CONFIG_VLAN_8021Q=y
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_CBQ=m
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_DSMARK=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_PKTGEN=m
+CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_MTD=y
+CONFIG_MTD_CMDLINE_PARTS=y
+CONFIG_MTD_BLOCK=y
+CONFIG_MTD_CFI=y
+CONFIG_MTD_JEDECPROBE=y
+CONFIG_MTD_CFI_INTELEXT=y
+CONFIG_MTD_CFI_AMDSTD=y
+CONFIG_MTD_CFI_STAA=y
+CONFIG_MTD_ROM=y
+CONFIG_MTD_ABSENT=y
+CONFIG_MTD_PHYSMAP_OF=y
+CONFIG_MTD_M25P80=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_GLUEBI=y
+CONFIG_PROC_DEVICETREE=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_RAM_SIZE=8192
+CONFIG_EEPROM_93CX6=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_BLK_DEV_SR=y
+CONFIG_CHR_DEV_SG=y
+CONFIG_SCSI_MULTI_LUN=y
+CONFIG_ATA=y
+CONFIG_SATA_AHCI_PLATFORM=y
+CONFIG_SATA_BRCMSTB=y
+CONFIG_MD=y
+CONFIG_BLK_DEV_DM=y
+CONFIG_DM_VERITY=y
+CONFIG_NETDEVICES=y
+# CONFIG_NET_VENDOR_ADAPTEC is not set
+# CONFIG_NET_VENDOR_ALTEON is not set
+# CONFIG_NET_VENDOR_AMD is not set
+# CONFIG_NET_VENDOR_ARC is not set
+# CONFIG_NET_VENDOR_ATHEROS is not set
+# CONFIG_NET_CADENCE is not set
+CONFIG_BCMGENET=y
+# CONFIG_NET_VENDOR_BROCADE is not set
+# CONFIG_NET_VENDOR_CHELSIO is not set
+# CONFIG_NET_VENDOR_CIRRUS is not set
+# CONFIG_NET_VENDOR_CISCO is not set
+# CONFIG_NET_VENDOR_DEC is not set
+# CONFIG_NET_VENDOR_DLINK is not set
+# CONFIG_NET_VENDOR_EMULEX is not set
+# CONFIG_NET_VENDOR_EXAR is not set
+# CONFIG_NET_VENDOR_FARADAY is not set
+# CONFIG_NET_VENDOR_HP is not set
+CONFIG_E1000E=y
+# CONFIG_NET_VENDOR_MARVELL is not set
+# CONFIG_NET_VENDOR_MELLANOX is not set
+# CONFIG_NET_VENDOR_MICREL is not set
+# CONFIG_NET_VENDOR_MICROCHIP is not set
+# CONFIG_NET_VENDOR_MYRI is not set
+# CONFIG_NET_VENDOR_NATSEMI is not set
+# CONFIG_NET_VENDOR_NVIDIA is not set
+# CONFIG_NET_VENDOR_OKI is not set
+# CONFIG_NET_PACKET_ENGINE is not set
+# CONFIG_NET_VENDOR_QLOGIC is not set
+# CONFIG_NET_VENDOR_REALTEK is not set
+# CONFIG_NET_VENDOR_RDC is not set
+# CONFIG_NET_VENDOR_SEEQ is not set
+# CONFIG_NET_VENDOR_SILAN is not set
+# CONFIG_NET_VENDOR_SIS is not set
+# CONFIG_NET_VENDOR_SMSC is not set
+# CONFIG_NET_VENDOR_STMICRO is not set
+# CONFIG_NET_VENDOR_SUN is not set
+# CONFIG_NET_VENDOR_TEHUTI is not set
+# CONFIG_NET_VENDOR_TI is not set
+# CONFIG_NET_VENDOR_VIA is not set
+# CONFIG_NET_VENDOR_WIZNET is not set
+CONFIG_USB_PEGASUS=y
+CONFIG_USB_USBNET=y
+# CONFIG_USB_NET_NET1080 is not set
+# CONFIG_USB_NET_CDC_SUBSET is not set
+# CONFIG_USB_NET_ZAURUS is not set
+# CONFIG_INPUT_MOUSEDEV_PSAUX is not set
+CONFIG_INPUT_EVDEV=y
+# CONFIG_INPUT_KEYBOARD is not set
+# CONFIG_INPUT_MOUSE is not set
+# CONFIG_SERIO is not set
+# CONFIG_CONSOLE_TRANSLATIONS is not set
+# CONFIG_VT_CONSOLE is not set
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_DW=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_HW_RANDOM=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_SPI=y
+CONFIG_SPI_BITBANG=y
+CONFIG_GPIOLIB=y
+CONFIG_POWER_SUPPLY=y
+CONFIG_POWER_RESET=y
+# CONFIG_HWMON is not set
+CONFIG_THERMAL=y
+CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE=y
+CONFIG_THERMAL_GOV_STEP_WISE=y
+CONFIG_CPU_THERMAL=y
+CONFIG_INTEL_POWERCLAMP=y
+CONFIG_BRCMSTB_THERMAL=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_MEDIA_CAMERA_SUPPORT=y
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=y
+# CONFIG_VGA_ARB is not set
+CONFIG_SOUND=y
+CONFIG_SND=y
+CONFIG_SND_SOC=y
+CONFIG_UHID=m
+# CONFIG_HID_GENERIC is not set
+CONFIG_HID_GFRM=m
+CONFIG_USB=y
+CONFIG_USB_MON=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_SERIAL=y
+CONFIG_USB_SERIAL_FTDI_SIO=y
+CONFIG_USB_SERIAL_KEYSPAN=y
+CONFIG_USB_SERIAL_PL2303=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=64
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_BRCMSTB_BMEM=y
+CONFIG_BRCMSTB_CMA=y
+CONFIG_BRCMSTB_MEMORY_API=y
+CONFIG_BRCMSTB_SRPD=y
+CONFIG_BRCMSTB_WKTMR=y
+CONFIG_BRCMSTB_NEXUS_API=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_RESET_CONTROLLER=y
+CONFIG_GENERIC_PHY=y
+CONFIG_EXT4_FS=y
+CONFIG_JBD2_DEBUG=y
+CONFIG_FUSE_FS=y
+CONFIG_CUSE=y
+CONFIG_ISO9660_FS=y
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=y
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_JFFS2_FS=y
+CONFIG_UBIFS_FS=y
+CONFIG_CRAMFS=y
+CONFIG_SQUASHFS=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_INFO_REDUCED=y
+CONFIG_DEBUG_FS=y
+CONFIG_DEBUG_SECTION_MISMATCH=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_LOCKUP_DETECTOR=y
+CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
+CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=30
+CONFIG_BOOTPARAM_HUNG_TASK_PANIC=y
+CONFIG_PANIC_ON_OOPS=y
+CONFIG_DEBUG_USER=y
+CONFIG_DEBUG_LL=y
+CONFIG_DEBUG_BRCMSTB_UART=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_CMAC=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_ARC4=y
+# CONFIG_CRYPTO_ANSI_CPRNG is not set
+# CONFIG_CRYPTO_HW is not set
+CONFIG_CRC_CCITT=y
diff --git a/arch/arm/mach-bcm/brcmstb.c b/arch/arm/mach-bcm/brcmstb.c
index 33b8bbd..ff6d25a 100644
--- a/arch/arm/mach-bcm/brcmstb.c
+++ b/arch/arm/mach-bcm/brcmstb.c
@@ -82,7 +82,7 @@
 }
 #endif
 
-DT_MACHINE_START(BRCMSTB, "Broadcom STB (Flattened Device Tree)")
+DT_MACHINE_START(BRCMSTB, "Broadcom STB GFHD254 (Flattened Device Tree)")
 	.dt_compat	= brcmstb_match,
 	.init_irq	= brcmstb_init_irq,
 #if defined(CONFIG_BRCMSTB)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 15338af..5a0d95c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -316,6 +316,12 @@
 	---help---
 	Support for Waltop tablets.
 
+config HID_GFRM
+	tristate "Google Fiber TV remote control support"
+	depends on HID
+	---help---
+	Support for Google Fiber TV remote controls
+
 config HID_GYRATION
 	tristate "Gyration remote control"
 	depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e4a21df..63de9e1 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -36,6 +36,7 @@
 obj-$(CONFIG_HID_ELECOM)	+= hid-elecom.o
 obj-$(CONFIG_HID_ELO)		+= hid-elo.o
 obj-$(CONFIG_HID_EZKEY)		+= hid-ezkey.o
+obj-$(CONFIG_HID_GFRM)		+= hid-gfrm.o
 obj-$(CONFIG_HID_GT683R)	+= hid-gt683r.o
 obj-$(CONFIG_HID_GYRATION)	+= hid-gyration.o
 obj-$(CONFIG_HID_HOLTEK)	+= hid-holtek-kbd.o
diff --git a/drivers/hid/hid-gfrm.c b/drivers/hid/hid-gfrm.c
new file mode 100644
index 0000000..7c1079b
--- /dev/null
+++ b/drivers/hid/hid-gfrm.c
@@ -0,0 +1,382 @@
+/*
+ * HID driver for Google Fiber TV remote controls
+ *
+ * Copyright (c) 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include "hid-ids.h"
+
+#define GFRM100  1  /* Google Fiber GFRM100 (Bluetooth classic) */
+#define GFRM200  2  /* Google Fiber GFRM200 (Bluetooth LE) */
+#define TIARC    3  /* Texas Instruments CC2541ARC (Bluetooth LE) */
+
+#define GFRM100_SEARCH_KEY_REPORT_ID   0xF7
+#define GFRM100_SEARCH_KEY_DOWN        0x0
+#define GFRM100_SEARCH_KEY_AUDIO_DATA  0x1
+#define GFRM100_SEARCH_KEY_UP          0x2
+
+static u8 search_key_dn[3] = {0x40, 0x21, 0x02};
+static u8 search_key_up[3] = {0x40, 0x00, 0x00};
+
+
+static const char* rawEventToButton(u8* data, int size)
+{
+	int code = 0xffff, key = 0xffff;		// aka unset
+	static char msg[128];
+
+	if (size < 1) goto unknown;
+
+	/* GFRM200: see "SRS_RC1534059_V0 11.pdf" */
+	/* strings match vendor/broadcom/drivers/bt/3rdparty/embedded/brcm/linux/bthid/bthid.c */
+
+	code = data[0];
+	switch (code) {
+	case 0x02:			// GFRM200 usage page 0x01, 0x06
+		if (size != 9) goto unknown;
+		key = data[3];
+		switch (key) {
+		case 0x00:    return "RELEASE2";
+		case 0x1E:    return "DIGIT_1";
+		case 0x1F:    return "DIGIT_2";
+		case 0x20:    return "DIGIT_3";
+		case 0x21:    return "DIGIT_4";
+		case 0x22:    return "DIGIT_5";
+		case 0x23:    return "DIGIT_6";
+		case 0x24:    return "DIGIT_7";
+		case 0x25:    return "DIGIT_8";
+		case 0x26:    return "DIGIT_9";
+		case 0x27:    return "DIGIT_0";
+		case 0x2A:    return "DIGIT_DEL";
+		case 0x4F:    return "RIGHT";
+		case 0x50:    return "LEFT";
+		case 0x51:    return "DOWN";
+		case 0x52:    return "UP";
+		}
+		break;
+
+	case 0x03:			// GFRM200 usage page 0x01, 0x0c
+		if (size != 3) goto unknown;
+		key = data[1] | (data[2] << 8);
+		switch (key) {
+		case 0x00:    return "RELEASE3";
+		case 0x04:    return "INFO";
+		case 0x30:    return "TV_BOX_POWER";
+		case 0x40:    return "MENU";
+		case 0x41:    return "OK";
+		case 0x7F:    return "TV_POWER";
+		case 0x83:    return "PREV";
+		case 0x8D:    return "GUIDE";
+		case 0x8E:    return "LIVE";
+		case 0x94:    return "EXIT";
+		case 0x97:    return "INPUT";
+		case 0x9C:    return "CH_UP";
+		case 0x9D:    return "CH_DOWN";
+		case 0xB0:    return "PLAY";
+		case 0xB1:    return "PAUSE";
+		case 0xB2:    return "RECORD";
+		case 0xB3:    return "FAST_FORWARD";
+		case 0xB4:    return "REWIND";
+		case 0xB5:    return "SKIP_FORWARD";
+		case 0xB6:    return "SKIP_BACKWARD";
+		case 0xB7:    return "STOP";
+		case 0xE2:    return "MUTE";
+		case 0xE9:    return "VOL_UP";
+		case 0xEA:    return "VOL_DOWN";
+		case 0x221:   return "SEARCH";
+		case 0x224:   return "BACK";
+		}
+		break;
+
+	case 0x81:			// GFRM200 usage page 0xff, 0x00
+		if (size != 2) goto unknown;
+		key = data[1];
+		switch (key) {
+		case 0x00:    return "RELEASE81";
+		}
+		break;
+
+	case 0x12:			// GFRM100 type 0x12
+		if (size != 3) goto unknown;
+		key = data[1];
+		switch (key) {
+		case 0x00:    return "RELEASE12";
+		case 0x82:    return "TV_POWER";
+		}
+		break;
+
+	case 0x13:			// GFRM100 type 0x13
+		if (size != 2) goto unknown;
+		key = data[1];
+		sprintf(msg, "BATTERY_LEVEL=%d", key);	// percent 0-100?
+		return msg;
+		break;
+
+	case 0x40:			// GFRM100 type 0x40
+		if (size != 3) goto unknown;
+		key = data[1] | (data[2] << 8);
+		switch (key) {
+		case 0x00:    return "RELEASE40";
+		case 0x04:    return "INFO";
+		case 0x41:    return "OK";
+		case 0x42:    return "UP";
+		case 0x43:    return "DOWN";
+		case 0x44:    return "LEFT";
+		case 0x45:    return "RIGHT";
+		case 0x30:    return "TV_BOX_POWER";
+		case 0x40:    return "MENU";
+		case 0x82:    return "INPUT";
+		case 0x83:    return "PREV";
+		case 0x8D:    return "GUIDE";
+		case 0x9C:    return "CH_UP";
+		case 0x9D:    return "CH_DOWN";
+		case 0xB0:    return "PLAY";
+		case 0xB1:    return "PAUSE";
+		case 0xB2:    return "RECORD";
+		case 0xB3:    return "FAST_FORWARD";
+		case 0xB4:    return "REWIND";
+		case 0xB5:    return "SKIP_FORWARD";
+		case 0xB6:    return "SKIP_BACKWARD";
+		case 0xB7:    return "STOP";
+		case 0xE2:    return "MUTE";
+		case 0xE9:    return "VOL_UP";
+		case 0xEA:    return "VOL_DOWN";
+		case 0x204:   return "EXIT";
+		case 0x224:   return "BACK";
+		}
+		break;
+
+	case 0x41:			// GFRM100 type 0x41
+		if (size != 2) goto unknown;
+		key = data[1];
+		switch (key) {
+		case 0x00:    return "RELEASE41";
+		case 0x1E:    return "DIGIT_1";
+		case 0x1F:    return "DIGIT_2";
+		case 0x20:    return "DIGIT_3";
+		case 0x21:    return "DIGIT_4";
+		case 0x22:    return "DIGIT_5";
+		case 0x23:    return "DIGIT_6";
+		case 0x24:    return "DIGIT_7";
+		case 0x25:    return "DIGIT_8";
+		case 0x26:    return "DIGIT_9";
+		case 0x27:    return "DIGIT_0";
+		case 0x2a:    return "DIGIT_DEL";
+		}
+		break;
+
+	case 0xf7:			// GFRM100 type 0xf7
+		if (size != 10) goto unknown;
+		key = data[1];
+		switch (key) {
+		case 0x00:   return "SEARCH";
+		case 0x02:   return "RELEASEF7";
+		}
+		break;
+	}
+unknown:
+	sprintf(msg, "UNKNOWN=%04x-%04x-%04x", code, key, size);
+	return msg;
+}
+
+static void dataToHexString(char* str, int strLen, u8* data, int dataLen)
+{
+	static char* hex = "0123456789abcdef";
+	char* sp = str;
+	char* esp = str + strLen;
+	u8* dp = data;
+	u8* edp = data + dataLen;
+
+	if (strLen < 1) return;
+
+	while (sp + 3 < esp && dp < edp) {
+		*sp++ = hex[(*dp >> 4) & 0x0f];
+		*sp++ = hex[(*dp >> 0) & 0x0f];
+		dp++;
+	}
+	*sp = '\0';
+}
+
+static void logButton(struct hid_device *hdev, struct hid_report *report,
+		u8 *data, int size)
+{
+	char dataStr[1024];
+	const char* button = rawEventToButton(data, size);
+
+	dataToHexString(dataStr, sizeof dataStr, data, size);
+
+	// for backward compatibility with log parsers, use this format:
+	// BTHID bthid_write: [00241cb74c8f]Sending report to HID: {1f4102} -> KEY_DIGIT_2
+	printk(KERN_INFO "BTHID bthid_write: Sending report to HID: {%s} -> KEY_%s\n", dataStr, button);
+}
+
+static int gfrm_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	int hdev_type = (int)hid_get_drvdata(hdev);
+
+	if (hdev_type == GFRM100) {
+		if (usage->hid == (HID_UP_CONSUMER | 0x4)) {
+			/* Consumer.0004 -> KEY_INFO */
+			hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_INFO);
+			return 1;
+		}
+
+		if (usage->hid == (HID_UP_CONSUMER | 0x41)) {
+			/* Consumer.0041 -> KEY_OK */
+			hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_OK);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int gfrm_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	int hdev_type = (int)hid_get_drvdata(hdev);
+
+	if (hdev_type == TIARC) {
+		if (usage->code == KEY_LEFTMETA) {
+			hid_map_usage(hi, usage, bit, max, usage->type, KEY_MENU);
+		} else if (usage->code == KEY_BACKSPACE) {
+			hid_map_usage(hi, usage, bit, max, usage->type, KEY_BACK);
+		} else if (usage->code == KEY_MUTE) {
+			hid_map_usage(hi, usage, bit, max, usage->type, KEY_PROGRAM);
+		}
+	}
+
+	return 0;
+}
+
+static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *data, int size)
+{
+	int hdev_type = (int)hid_get_drvdata(hdev);
+	int ret = 0;
+
+	logButton(hdev, report, data, size);
+
+	if (hdev_type != GFRM100)
+		return 0;
+
+	if (size < 2 || data[0] != GFRM100_SEARCH_KEY_REPORT_ID)
+		return 0;
+
+	/*
+	 * Convert GFRM100 Search key reports into Consumer.0221 (Key.Search)
+	 * reports. Ignore audio data.
+	 */
+	switch (data[1]) {
+	case GFRM100_SEARCH_KEY_DOWN:
+		ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
+					   sizeof(search_key_dn), 1);
+		break;
+
+	case GFRM100_SEARCH_KEY_AUDIO_DATA:
+		break;
+
+	case GFRM100_SEARCH_KEY_UP:
+		ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
+					   sizeof(search_key_up), 1);
+		break;
+
+	default:
+		break;
+	}
+
+	return (ret < 0) ? ret : -1;
+}
+
+static void gfrm_input_configured(struct hid_device *hid, struct hid_input *hidinput)
+{
+	/*
+	 * Enable software autorepeat with:
+	 * - repeat delay: 400 msec
+	 * - repeat period: 100 msec
+	 * - repeat maximum count: 30
+	 */
+	input_enable_softrepeat(hidinput->input, 400, 100, 30);
+}
+
+static int gfrm_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	hid_set_drvdata(hdev, (void *)id->driver_data);
+
+	ret = hid_parse(hdev);
+	if (ret)
+		goto done;
+
+	if (id->driver_data == GFRM100) {
+		/*
+		 * GFRM100 HID Report Descriptor does not describe the Search
+		 * key reports. Thus, we need to add it manually here, so that
+		 * those reports reach gfrm_raw_event() from hid_input_report().
+		 */
+		if (!hid_register_report(hdev, HID_INPUT_REPORT,
+					 GFRM100_SEARCH_KEY_REPORT_ID)) {
+			ret = -ENOMEM;
+			goto done;
+		}
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+done:
+	return ret;
+}
+
+static void gfrm_remove(struct hid_device *hdev)
+{
+	hid_hw_stop(hdev);
+	hid_set_drvdata(hdev, NULL);
+}
+
+static const struct hid_device_id gfrm_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(0x58, 0x2000),
+		.driver_data = GFRM100 },
+	{ HID_BLUETOOTH_DEVICE(0x471, 0x2210),
+		.driver_data = GFRM200 },
+	{ HID_BLUETOOTH_DEVICE(0xD, 0x0),
+		.driver_data = TIARC },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, gfrm_devices);
+
+static struct hid_driver gfrm_driver = {
+	.name = "gfrm",
+	.id_table = gfrm_devices,
+	.probe = gfrm_probe,
+	.remove = gfrm_remove,
+	.input_mapping = gfrm_input_mapping,
+	.input_mapped = gfrm_input_mapped,
+	.raw_event = gfrm_raw_event,
+	.input_configured = gfrm_input_configured,
+};
+
+static int __init gfrm_init(void)
+{
+	return hid_register_driver(&gfrm_driver);
+}
+
+static void __exit gfrm_exit(void)
+{
+	hid_unregister_driver(&gfrm_driver);
+}
+
+module_init(gfrm_init);
+module_exit(gfrm_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 32d52d2..5129884 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -749,6 +749,8 @@
 	case HID_UP_CONSUMER:	/* USB HUT v1.12, pages 75-84 */
 		switch (usage->hid & HID_USAGE) {
 		case 0x000: goto ignore;
+		case 0x004: map_key_clear(KEY_INFO);		break; /* GFRM200 */
+
 		case 0x030: map_key_clear(KEY_POWER);		break;
 		case 0x031: map_key_clear(KEY_RESTART);		break;
 		case 0x032: map_key_clear(KEY_SLEEP);		break;
@@ -757,7 +759,7 @@
 		case 0x036: map_key_clear(BTN_MISC);		break;
 
 		case 0x040: map_key_clear(KEY_MENU);		break; /* Menu */
-		case 0x041: map_key_clear(KEY_SELECT);		break; /* Menu Pick */
+		case 0x041: map_key_clear(KEY_OK);		break; /* Menu Pick, GFRM200 */
 		case 0x042: map_key_clear(KEY_UP);		break; /* Menu Up */
 		case 0x043: map_key_clear(KEY_DOWN);		break; /* Menu Down */
 		case 0x044: map_key_clear(KEY_LEFT);		break; /* Menu Left */
@@ -792,7 +794,7 @@
 		case 0x08b: map_key_clear(KEY_DVD);		break;
 		case 0x08c: map_key_clear(KEY_PHONE);		break;
 		case 0x08d: map_key_clear(KEY_PROGRAM);		break;
-		case 0x08e: map_key_clear(KEY_VIDEOPHONE);	break;
+		case 0x08e: map_key_clear(KEY_TV);		break; /* GFRM200 live */
 		case 0x08f: map_key_clear(KEY_GAMES);		break;
 		case 0x090: map_key_clear(KEY_MEMO);		break;
 		case 0x091: map_key_clear(KEY_CD);		break;
@@ -801,7 +803,7 @@
 		case 0x094: map_key_clear(KEY_EXIT);		break;
 		case 0x095: map_key_clear(KEY_HELP);		break;
 		case 0x096: map_key_clear(KEY_TAPE);		break;
-		case 0x097: map_key_clear(KEY_TV2);		break;
+		case 0x097: map_key_clear(KEY_TV2);		break; /* GFRM200 input */
 		case 0x098: map_key_clear(KEY_SAT);		break;
 		case 0x09a: map_key_clear(KEY_PVR);		break;
 
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 2ae522f..d32e7f5 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -877,24 +877,34 @@
 		return 0;
 
 	case EVIOCGREP:
+	case EVIOCGREP_V2:
 		if (!test_bit(EV_REP, dev->evbit))
 			return -ENOSYS;
 		if (put_user(dev->rep[REP_DELAY], ip))
 			return -EFAULT;
 		if (put_user(dev->rep[REP_PERIOD], ip + 1))
 			return -EFAULT;
+		if (cmd == EVIOCGREP_V2 &&
+		    put_user(dev->rep[REP_MAX_COUNT], ip + 2))
+			return -EFAULT;
 		return 0;
 
 	case EVIOCSREP:
+	case EVIOCSREP_V2:
 		if (!test_bit(EV_REP, dev->evbit))
 			return -ENOSYS;
 		if (get_user(u, ip))
 			return -EFAULT;
 		if (get_user(v, ip + 1))
 			return -EFAULT;
+		if (cmd == EVIOCSREP_V2 && get_user(t, ip + 2))
+			return -EFAULT;
 
 		input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u);
 		input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v);
+		if (cmd == EVIOCSREP_V2)
+			input_inject_event(&evdev->handle, EV_REP,
+					   REP_MAX_COUNT, t);
 
 		return 0;
 
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 3b61c97..4e9f7cd 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -78,6 +78,7 @@
 	    dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] &&
 	    dev->timer.data) {
 		dev->repeat_key = code;
+		dev->repeat_count = 0;
 		mod_timer(&dev->timer,
 			  jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
 	}
@@ -197,7 +198,9 @@
 
 		input_pass_values(dev, vals, ARRAY_SIZE(vals));
 
-		if (dev->rep[REP_PERIOD])
+		if (dev->rep[REP_PERIOD] &&
+		    (dev->rep[REP_MAX_COUNT] == 0 ||
+		     ++dev->repeat_count < dev->rep[REP_MAX_COUNT]))
 			mod_timer(&dev->timer, jiffies +
 					msecs_to_jiffies(dev->rep[REP_PERIOD]));
 	}
@@ -1653,6 +1656,7 @@
 	if (activate && test_bit(EV_REP, dev->evbit)) {
 		dev->event(dev, EV_REP, REP_PERIOD, dev->rep[REP_PERIOD]);
 		dev->event(dev, EV_REP, REP_DELAY, dev->rep[REP_DELAY]);
+		dev->event(dev, EV_REP, REP_MAX_COUNT, dev->rep[REP_MAX_COUNT]);
 	}
 }
 
@@ -2059,6 +2063,26 @@
 }
 
 /**
+ * input_enable_softrepeat - enable software autorepeat
+ * @dev: input device
+ * @delay: repeat delay
+ * @period: repeat period
+ * @max_count: repeat maximum count
+ *
+ * Enable software autorepeat on the input device.
+ */
+void input_enable_softrepeat(struct input_dev *dev, int delay, int period,
+			     int max_count)
+{
+	dev->timer.data = (unsigned long) dev;
+	dev->timer.function = input_repeat_key;
+	dev->rep[REP_DELAY] = delay;
+	dev->rep[REP_PERIOD] = period;
+	dev->rep[REP_MAX_COUNT] = max_count;
+}
+EXPORT_SYMBOL(input_enable_softrepeat);
+
+/**
  * input_register_device - register device with input core
  * @dev: device to be registered
  *
@@ -2122,12 +2146,8 @@
 	 * If delay and period are pre-set by the driver, then autorepeating
 	 * is handled by the driver itself and we don't do it in input.c.
 	 */
-	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
-		dev->timer.data = (long) dev;
-		dev->timer.function = input_repeat_key;
-		dev->rep[REP_DELAY] = 250;
-		dev->rep[REP_PERIOD] = 33;
-	}
+	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
+		input_enable_softrepeat(dev, 250, 33, 0);
 
 	if (!dev->getkeycode)
 		dev->getkeycode = input_default_getkeycode;
diff --git a/drivers/pci/host/pci-brcmstb.c b/drivers/pci/host/pci-brcmstb.c
index f5f399f..60cd9f3 100644
--- a/drivers/pci/host/pci-brcmstb.c
+++ b/drivers/pci/host/pci-brcmstb.c
@@ -743,7 +743,10 @@
 
 	/* Refclk from RC should be gated with CLKREQ# input when ASPM L0s,L1
 	 * is enabled =>  setting the CLKREQ_DEBUG_ENABLE field to 1. */
-	wr_fld_rb(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG, 0x00000002, 1, 1);
+
+        // TODO(jnewlin): Temporarily comment this out.  It's needed because the
+        // Google board has a pullup on pcie_clkreq_l which disable the clock.
+//	wr_fld_rb(base + PCIE_MISC_HARD_PCIE_HARD_DEBUG, 0x00000002, 1, 1);
 
 	/* Add bogus IO resource structure so that pcibios_init_resources()
 	 * does not allocate the same IO region for different domains */
diff --git a/include/linux/input.h b/include/linux/input.h
index 82ce323..dba6d07 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -151,6 +151,7 @@
 	struct ff_device *ff;
 
 	unsigned int repeat_key;
+	unsigned int repeat_count;
 	struct timer_list timer;
 
 	int rep[REP_CNT];
@@ -469,6 +470,9 @@
 int input_set_keycode(struct input_dev *dev,
 		      const struct input_keymap_entry *ke);
 
+void input_enable_softrepeat(struct input_dev *dev, int delay, int period,
+			     int max_count);
+
 extern struct class input_class;
 
 /**
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index 731417c..1840afc 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -100,7 +100,9 @@
 #define EVIOCGVERSION		_IOR('E', 0x01, int)			/* get driver version */
 #define EVIOCGID		_IOR('E', 0x02, struct input_id)	/* get device ID */
 #define EVIOCGREP		_IOR('E', 0x03, unsigned int[2])	/* get repeat settings */
+#define EVIOCGREP_V2		_IOR('E', 0x03, unsigned int[3])
 #define EVIOCSREP		_IOW('E', 0x03, unsigned int[2])	/* set repeat settings */
+#define EVIOCSREP_V2		_IOW('E', 0x03, unsigned int[3])
 
 #define EVIOCGKEYCODE		_IOR('E', 0x04, unsigned int[2])        /* get keycode */
 #define EVIOCGKEYCODE_V2	_IOR('E', 0x04, struct input_keymap_entry)
@@ -930,7 +932,8 @@
 
 #define REP_DELAY		0x00
 #define REP_PERIOD		0x01
-#define REP_MAX			0x01
+#define REP_MAX_COUNT		0x02
+#define REP_MAX			0x02
 #define REP_CNT			(REP_MAX+1)
 
 /*
diff --git a/init/Kconfig b/init/Kconfig
index dc24dec..2660eaa 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1445,6 +1445,27 @@
 	  very difficult to diagnose system problems, saying N here is
 	  strongly discouraged.
 
+config PRINTK_PERSIST
+	default n
+	bool "printk log persists across reboots" if PRINTK
+	help
+	  This option tries to keep the printk memory buffer in a well-known
+	  location in physical memory. It isn't cleared on reboot (unless RAM
+	  is wiped by your boot loader or BIOS) so if your system crashes
+	  or panics, you might get to examine all the log messages next time you
+	  boot. The persisted log messages show up in your 'dmesg' output.
+	  Note: you must supply the log_buf_len= kernel parameter to
+	  activate this feature.
+
+config BOOTLOG_COPY
+	default n
+	bool "copy boot log to printk log" if PRINTK
+	help
+	  This option copies the boot log stored in bootlog memory and save it
+	  in the printk log.
+	  Note: you must supply the bootlog= and log_buf_len= kernel parameters
+	  to activate this feature.
+
 config BUG
 	bool "BUG() support" if EXPERT
 	default y
diff --git a/kernel/panic.c b/kernel/panic.c
index a4f7820..35799e8 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -8,6 +8,7 @@
  * This function is used through-out the kernel (including mm and fs)
  * to indicate a major problem.
  */
+#include <asm/cacheflush.h>
 #include <linux/debug_locks.h>
 #include <linux/interrupt.h>
 #include <linux/kmsg_dump.h>
@@ -177,6 +178,8 @@
 			mdelay(PANIC_TIMER_STEP);
 		}
 	}
+	printk(KERN_EMERG "Flushing cache..");
+	flush_cache_all();
 	if (panic_timeout != 0) {
 		/*
 		 * This will not be a clean reboot, with everything
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 3c1aca0..2e093f8 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -46,6 +46,7 @@
 #include <linux/utsname.h>
 #include <linux/ctype.h>
 #include <linux/uio.h>
+#include <linux/crc32.h>
 
 #include <asm/uaccess.h>
 
@@ -218,6 +219,9 @@
 	u16 text_len;		/* length of text buffer */
 	u16 dict_len;		/* length of dictionary buffer */
 	u8 facility;		/* syslog facility */
+#ifdef CONFIG_PRINTK_PERSIST
+	u32 crc;		/* the flags may change, so don't include them */
+#endif
 	u8 flags:5;		/* internal record flags */
 	u8 level:3;		/* syslog level */
 };
@@ -231,6 +235,99 @@
 
 #ifdef CONFIG_PRINTK
 DECLARE_WAIT_QUEUE_HEAD(log_wait);
+
+#define PREFIX_MAX		32
+#define LOG_LINE_MAX		(1024 - PREFIX_MAX)
+
+/* record buffer */
+#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
+#define LOG_ALIGN 4
+#else
+#define LOG_ALIGN __alignof__(struct printk_log)
+#endif
+#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
+static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
+static char *log_buf = __log_buf;
+
+static int log_make_free_space(u32 msg_size);
+static int log_store(int facility, int level,
+		     enum log_flags flags, u64 ts_nsec,
+		     const char *dict, u16 dict_len,
+		     const char *text, u16 text_len);
+
+#ifdef CONFIG_BOOTLOG_COPY
+#define BOOTLOG_MAGIC (0x1090091e)
+struct bloghdr {
+	unsigned int magic; /* for kernel verification */
+	unsigned int offset; /* current log offset */
+};
+
+extern unsigned long bootlog_get_addr(void);
+extern unsigned long bootlog_get_size(void);
+
+static __init inline struct bloghdr *get_bootlog_hdr(void)
+{
+	unsigned long bootlog_size = bootlog_get_size();
+	if (bootlog_size) {
+		struct bloghdr *blog_hdr = (struct bloghdr *)
+				phys_to_virt(bootlog_get_addr());
+		if (BOOTLOG_MAGIC != blog_hdr->magic ||
+		    (blog_hdr->offset + sizeof(struct bloghdr) >
+                     bootlog_size)) {
+			printk(KERN_INFO "bootlog: header invalid m:0x%08x "
+			       "o:0x%08x s:0x%08lx\n", blog_hdr->magic,
+                               blog_hdr->offset, bootlog_size);
+			return NULL;
+		}
+		return blog_hdr;
+	}
+        printk(KERN_INFO "bootlog: bootlog_size was 0.\n");
+	return NULL;
+}
+
+#define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
+
+static inline void __init copy_bootlog(struct bloghdr *blog_hdr)
+{
+	if (blog_hdr) {
+		char *blog_buf = (char *)(blog_hdr + 1);
+		int i,j;
+		char *tmp;
+		/* Strip out nonprintable characters from the bootlog. */
+		for (i=0,j=0; i < blog_hdr->offset; i++) {
+			if (printable(blog_buf[i])) {
+				blog_buf[j++] = blog_buf[i];
+			}
+		}
+		/* Loop over each line in the boot loader log, and insert them into the
+		 * kernel log one at a time.  Attempting to insert the entire thing fails due
+		 * to some logic that truncates long messages.
+		 */
+		tmp = &blog_buf[0];
+		for (i=0; i < j; i++) {
+			if (blog_buf[i] == '\n') {
+				/* Insert with facility=0, level=INFO, time=0, No dict. */
+				log_store(0, 6, LOG_NEWLINE, 0,
+					NULL, 0, tmp, &blog_buf[i] - tmp);
+				tmp = &blog_buf[i+1];
+			}
+		}
+
+		/* Insert any trailing line into the kernel buffer. */
+		if (tmp != &blog_buf[i]) {
+			log_store(0, 6, 0, 0,
+				NULL, 0, tmp, &blog_buf[i] - tmp);
+		}
+	}
+}
+
+static inline void __init free_bootlog(void)
+{
+	free_bootmem(bootlog_get_addr(), bootlog_get_size());
+}
+#endif
+
+#ifndef CONFIG_PRINTK_PERSIST
 /* the next printk record to read by syslog(READ) or /proc/kmsg */
 static u64 syslog_seq;
 static u32 syslog_idx;
@@ -254,20 +351,192 @@
 static u64 clear_seq;
 static u32 clear_idx;
 
-#define PREFIX_MAX		32
-#define LOG_LINE_MAX		(1024 - PREFIX_MAX)
-
-/* record buffer */
-#if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
-#define LOG_ALIGN 4
-#else
-#define LOG_ALIGN __alignof__(struct printk_log)
-#endif
-#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)
-static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
-static char *log_buf = __log_buf;
 static u32 log_buf_len = __LOG_BUF_LEN;
 
+#else  /*  CONFIG_PRINTK_PERSIST */
+
+struct logbits {
+	int magic; /* needed to verify the memory across reboots */
+	u32 _log_buf_len;
+	u64 _syslog_seq; /* leading _ so they aren't replaced by #define */
+	u32 _syslog_idx;
+	enum log_flags _syslog_prev;
+	size_t _syslog_partial;
+
+/* index and sequence number of the first record stored in the buffer */
+	u64 _log_first_seq;
+	u32 _log_first_idx;
+
+/* index and sequence number of the next record to store in the buffer */
+	u64 _log_next_seq;
+	u32 _log_next_idx;
+
+/* the next printk record to write to the console */
+	u64 _console_seq;
+	u32 _console_idx;
+	enum log_flags _console_prev;
+
+/* the next printk record to read after the last 'clear' command */
+	u64 _clear_seq;
+	u32 _clear_idx;
+};
+
+static struct logbits __logbits = {
+	._log_buf_len = __LOG_BUF_LEN,
+};
+static struct logbits *logbits = &__logbits;
+
+#define log_buf_len    logbits->_log_buf_len
+#define syslog_seq     logbits->_syslog_seq
+#define syslog_idx     logbits->_syslog_idx
+#define syslog_prev    logbits->_syslog_prev
+#define syslog_partial logbits->_syslog_partial
+#define log_first_seq  logbits->_log_first_seq
+#define log_first_idx  logbits->_log_first_idx
+#define log_next_seq   logbits->_log_next_seq
+#define log_next_idx   logbits->_log_next_idx
+#define console_seq    logbits->_console_seq
+#define console_idx    logbits->_console_idx
+#define console_prev   logbits->_console_prev
+#define clear_seq      logbits->_clear_seq
+#define clear_idx      logbits->_clear_idx
+
+#define PERSIST_SEARCH_START 0
+#define PERSIST_SEARCH_END 0xfe000000
+#define PERSIST_SEARCH_JUMP (16*1024*1024)
+#define PERSIST_MAGIC 0xba5eba11
+
+/*
+ * arm uses one memory model, mips uses another
+ */
+static __init phys_addr_t physmem_reserve(phys_addr_t size) {
+#ifdef CONFIG_NO_BOOTMEM
+	phys_addr_t alloc;
+	alloc = memblock_find_in_range_node(size, SMP_CACHE_BYTES,
+			PERSIST_SEARCH_START, PERSIST_SEARCH_END,
+			NUMA_NO_NODE);
+	if (!alloc) return alloc;
+	if (memblock_reserve(alloc, size)) {
+		pr_err("printk_persist: memblock_reserve failed\n");
+		return 0;
+	}
+	return alloc;
+#else
+	unsigned long where;
+	for (where = PERSIST_SEARCH_END - size;
+	     where >= PERSIST_SEARCH_START && where <= PERSIST_SEARCH_END - size;
+	     where -= PERSIST_SEARCH_JUMP) {
+		if (reserve_bootmem(where, size, BOOTMEM_EXCLUSIVE))
+			continue;
+		else
+			return where;
+	}
+	return 0;
+#endif
+}
+
+/*
+ * size is a power of 2 so that the printk offset mask will work.  We'll add
+ * a bit more space to the end of the buffer for our extra data, but that
+ * won't change the offset of the buffers.
+ */
+static __init struct logbits *log_buf_alloc(unsigned long size, char **new_logbuf)
+{
+	char *buf;
+	phys_addr_t alloc;
+	unsigned long full_size = size + sizeof(struct logbits);
+	struct logbits *new_logbits;
+	u64 seq;
+	int idx, lost_entries;
+	struct printk_log *prlog;
+	u32 curr_crc;
+
+	alloc = physmem_reserve(full_size);
+	if (alloc) {
+		buf = phys_to_virt(alloc);
+		*new_logbuf = buf;
+		new_logbits = (void*)buf + size;
+		printk(KERN_INFO "printk_persist: memory reserved @ %pa\n",
+			&alloc);
+		if ((new_logbits->magic != PERSIST_MAGIC) ||
+		    (new_logbits->_log_buf_len != size) ||
+			(new_logbits->_log_first_seq >
+				new_logbits->_log_next_seq) ||
+			(new_logbits->_log_first_idx > size) ||
+			(new_logbits->_syslog_idx > size) ||
+			(new_logbits->_syslog_seq >
+				new_logbits->_log_next_seq) ||
+			(new_logbits->_log_next_idx > size) ||
+			(new_logbits->_console_seq >
+				new_logbits->_log_next_seq) ||
+			(new_logbits->_console_idx > size) ||
+			(new_logbits->_clear_seq >
+				new_logbits->_log_next_seq) ||
+			(new_logbits->_clear_idx > size)) {
+			printk(KERN_INFO "printk_persist: header invalid, "
+				"cleared.\n");
+			memset(buf, 0, full_size);
+			memset(new_logbits, 0, sizeof(*new_logbits));
+			new_logbits->magic = PERSIST_MAGIC;
+			new_logbits->_log_buf_len = size;
+		} else {
+			printk(KERN_INFO "printk_persist: header valid; "
+				"log_first_idx=%d\n"
+				"log_next_idx=%d\n"
+				"log_first_seq=%lld\n"
+				"log_next_seq=%lld\n"
+				"console_seq=%lld\n"
+				"console_idx=%d\n"
+				"syslog_seq=%lld\n"
+				"syslog_idx=%d\n",
+				new_logbits->_log_first_idx,
+				new_logbits->_log_next_idx,
+				new_logbits->_log_first_seq,
+				new_logbits->_log_next_seq,
+				new_logbits->_console_seq,
+				new_logbits->_console_idx,
+				new_logbits->_syslog_seq,
+				new_logbits->_syslog_idx);
+			printk(KERN_INFO "printk_persist: validating records\n");
+			for (seq = new_logbits->_log_first_seq, idx = new_logbits->_log_first_idx;
+				seq < new_logbits->_log_next_seq; ++seq) {
+				prlog = (struct printk_log *) &buf[idx];
+				// Verify validity of this record. If its bad, then move the next
+				// pointer to be where this record is at.
+				curr_crc = crc32(~0, prlog, offsetof(struct printk_log, crc));
+				if (prlog->crc != curr_crc) {
+					lost_entries = (int)(new_logbits->_log_next_seq - seq);
+					printk(KERN_INFO "printk_persist: corruption found, lost %d entries\n",
+						lost_entries);
+					new_logbits->_log_next_seq = seq;
+					new_logbits->_log_next_idx = idx;
+					break;
+				}
+				if (prlog->len == 0) {
+					idx = 0;
+					// Do not increment the sequence counter for the
+					// record that's used to indicate wrapping. We
+					// do still want to verify its CRC above though.
+					--seq;
+				}
+				else
+					idx += prlog->len;
+			}
+		}
+	} else {
+		/* replace the buffer, but don't bother to swap struct logbits */
+		printk(KERN_ERR "printk_persist: failed to reserve bootmem "
+			"area. disabled.\n");
+		buf = alloc_bootmem(full_size);
+		*new_logbuf = buf;
+		new_logbits = (struct logbits*)(buf + size);
+		memset(buf, 0, full_size);
+	}
+
+	return new_logbits;
+}
+#endif
+
 /* Return log buffer address */
 char *log_buf_addr_get(void)
 {
@@ -433,11 +702,19 @@
 		 * to signify a wrap around.
 		 */
 		memset(log_buf + log_next_idx, 0, sizeof(struct printk_log));
+#ifdef CONFIG_PRINTK_PERSIST
+		((struct printk_log*) (log_buf + log_next_idx))->crc =
+			crc32(~0, log_buf + log_next_idx, offsetof(struct printk_log, crc));
+#endif
 		log_next_idx = 0;
 	}
 
+	/* insert message */
+	log_next_idx += size;
+	log_next_seq++;
+
 	/* fill message */
-	msg = (struct printk_log *)(log_buf + log_next_idx);
+	msg = (struct printk_log *)(log_buf + log_next_idx - size);
 	memcpy(log_text(msg), text, text_len);
 	msg->text_len = text_len;
 	if (trunc_msg_len) {
@@ -455,10 +732,9 @@
 		msg->ts_nsec = local_clock();
 	memset(log_dict(msg) + dict_len, 0, pad_len);
 	msg->len = size;
-
-	/* insert message */
-	log_next_idx += msg->len;
-	log_next_seq++;
+#ifdef CONFIG_PRINTK_PERSIST
+	msg->crc = crc32(~0, msg, offsetof(struct printk_log, crc));
+#endif
 
 	return msg->text_len;
 }
@@ -693,6 +969,10 @@
 	struct devkmsg_user *user = file->private_data;
 	loff_t ret = 0;
 
+	/* glibc's fdopen() calls _llseek(..., SEEK_CUR). Let's make it happy
+	 * by returning 0 instead of an error. */
+	if (offset == 0 && whence == SEEK_CUR)
+		return 0;
 	if (!user)
 		return -EBADF;
 	if (offset)
@@ -887,6 +1167,17 @@
 	unsigned long flags;
 	char *new_log_buf;
 	int free;
+#ifdef CONFIG_PRINTK_PERSIST
+	struct logbits *new_logbits;
+	struct logbits *old_logbits;
+	struct printk_log *prlog;
+	u64 seq;
+	int idx;
+	int console_found=0, syslog_found=0;
+#endif
+#ifdef CONFIG_BOOTLOG_COPY
+	struct bloghdr *blog_hdr = NULL;
+#endif
 
 	if (log_buf != __log_buf)
 		return;
@@ -897,6 +1188,7 @@
 	if (!new_log_buf_len)
 		return;
 
+#ifndef CONFIG_PRINTK_PERSIST
 	if (early) {
 		new_log_buf =
 			memblock_virt_alloc(new_log_buf_len, LOG_ALIGN);
@@ -904,6 +1196,9 @@
 		new_log_buf = memblock_virt_alloc_nopanic(new_log_buf_len,
 							  LOG_ALIGN);
 	}
+#else
+	new_logbits = log_buf_alloc(new_log_buf_len, &new_log_buf);
+#endif
 
 	if (unlikely(!new_log_buf)) {
 		pr_err("log_buf_len: %ld bytes not available\n",
@@ -911,14 +1206,93 @@
 		return;
 	}
 
+#ifdef CONFIG_BOOTLOG_COPY
+	/* Read out the blog_hdr before logbuf is locked in case print
+	 * is needed. */
+	blog_hdr = get_bootlog_hdr();
+#endif
+
 	raw_spin_lock_irqsave(&logbuf_lock, flags);
 	log_buf_len = new_log_buf_len;
 	log_buf = new_log_buf;
 	new_log_buf_len = 0;
 	free = __LOG_BUF_LEN - log_next_idx;
+
+#ifndef CONFIG_PRINTK_PERSIST
 	memcpy(log_buf, __log_buf, __LOG_BUF_LEN);
+#else
+	/* We have to copy over entries one at a time from the old
+	 * buffer to the new buffer.
+	 */
+	old_logbits = logbits;
+	logbits = new_logbits;
+
+#ifdef CONFIG_BOOTLOG_COPY
+	copy_bootlog(blog_hdr);
+#endif
+
+	for (seq = old_logbits->_log_first_seq, idx = old_logbits->_log_first_idx;
+		 seq < old_logbits->_log_next_seq; ++seq) {
+		prlog = (struct printk_log *)&__log_buf[idx];
+		if (prlog->len == 0) {
+			idx = 0;
+			prlog = (struct printk_log *)&__log_buf[0];
+		}
+
+		if (log_make_free_space(prlog->len)) {
+			pr_err("not copying entry due to it being too huge\n");
+			idx += prlog->len;
+			continue;
+		}
+
+		if (log_next_idx + prlog->len + sizeof(struct printk_log) > log_buf_len) {
+			/*
+			 * This message + an additional empty header does not fit
+			 * at the end of the buffer. Add an empty header with len == 0
+			 * to signify a wrap around.
+			 */
+			memset(log_buf + log_next_idx, 0, sizeof(struct printk_log));
+#ifdef CONFIG_PRINTK_PERSIST
+			((struct printk_log*) (log_buf + log_next_idx))->crc =
+				crc32(~0, log_buf + log_next_idx, offsetof(struct printk_log, crc));
+#endif
+			log_next_idx = 0;
+		}
+		memcpy(&log_buf[log_next_idx], &__log_buf[idx], prlog->len);
+
+		if (old_logbits->_syslog_seq == seq) {
+			syslog_seq = log_next_seq;
+			syslog_idx = log_next_idx;
+			syslog_found = 1;
+		}
+
+		if (old_logbits->_console_seq == seq) {
+			console_seq = log_next_seq;
+			console_idx = log_next_idx;
+			console_found = 1;
+		}
+
+		idx += prlog->len;
+		log_next_seq++;
+		log_next_idx += prlog->len;
+	}
+
+	if (!syslog_found) {
+		syslog_seq = log_next_seq;
+		syslog_idx = log_next_idx;
+	}
+
+	if (!console_found) {
+		console_seq = log_next_seq;
+		console_idx = log_next_idx;
+	}
+#endif
 	raw_spin_unlock_irqrestore(&logbuf_lock, flags);
 
+#ifdef CONFIG_BOOTLOG_COPY
+	free_bootlog();
+#endif
+
 	pr_info("log_buf_len: %d bytes\n", log_buf_len);
 	pr_info("early log buf free: %d(%d%%)\n",
 		free, (free * 100) / __LOG_BUF_LEN);