diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index 1d6f045..156911b 100644
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -3339,6 +3339,12 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 
 	ro		[KNL] Mount root device read-only on boot
 
+	rockchip.usb_uart
+			Enable the uart passthrough on the designated usb port
+			on Rockchip SoCs. When active, the signals of the
+			debug-uart get routed to the D+ and D- pins of the usb
+			port and the regular usb controller gets disabled.
+
 	root=		[KNL] Root filesystem
 			See name_to_dev_t comment in init/do_mounts.c.
 
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 96ddecb..ecb1a6c 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers"
 
 source "drivers/soc/mediatek/Kconfig"
 source "drivers/soc/qcom/Kconfig"
+source "drivers/soc/rockchip/Kconfig"
 source "drivers/soc/sunxi/Kconfig"
 source "drivers/soc/ti/Kconfig"
 source "drivers/soc/versatile/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 7dc7c0d..6c0b677 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -4,6 +4,7 @@
 
 obj-$(CONFIG_ARCH_MEDIATEK)	+= mediatek/
 obj-$(CONFIG_ARCH_QCOM)		+= qcom/
+obj-$(CONFIG_ARCH_ROCKCHIP)	+= rockchip/
 obj-$(CONFIG_ARCH_SUNXI)	+= sunxi/
 obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
 obj-$(CONFIG_SOC_TI)		+= ti/
diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig
new file mode 100644
index 0000000..24d4e05
--- /dev/null
+++ b/drivers/soc/rockchip/Kconfig
@@ -0,0 +1,13 @@
+#
+# Rockchip Soc drivers
+#
+config ROCKCHIP_USB_UART
+	bool "Rockchip usb-uart override"
+	depends on ARCH_ROCKCHIP
+	select MFD_SYSCON
+	help
+	  Say y here to enable usb-uart functionality. Newer Rockchip SoCs
+	  provide means to repurpose one usb phy as uart2 output, making it
+	  possible to get debug output without needing to open a device.
+	  To enable this function on boot, add a rockchip.usb_uart option
+	  to the kernel commandline.
diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile
new file mode 100644
index 0000000..b5dd6f8
--- /dev/null
+++ b/drivers/soc/rockchip/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_ROCKCHIP_USB_UART)		+=	rockchip_usb_uart.o
diff --git a/drivers/soc/rockchip/rockchip_usb_uart.c b/drivers/soc/rockchip/rockchip_usb_uart.c
new file mode 100644
index 0000000..8e96d46
--- /dev/null
+++ b/drivers/soc/rockchip/rockchip_usb_uart.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2015 Heiko Stuebner <heiko@sntech.de>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define HIWORD_UPDATE(val, mask) \
+		((val) | (mask) << 16)
+
+struct rockchip_uart_data {
+	const char *grf_compatible;
+	const char *usb_compatible;
+	phys_addr_t usb_phys_addr;
+	int (*init_uart)(const struct rockchip_uart_data *data,
+			 struct regmap *grf);
+};
+
+static int enable_usb_uart;
+
+#define RK3288_UOC0_CON0				0x320
+#define RK3288_UOC0_CON0_COMMON_ON_N			BIT(0)
+#define RK3288_UOC0_CON0_DISABLE			BIT(4)
+#define RK3288_UOC0_CON0_SIDDQ				BIT(13)
+
+#define RK3288_UOC0_CON2				0x328
+#define RK3288_UOC0_CON2_SOFT_CON_SEL			BIT(2)
+
+#define RK3288_UOC0_CON3				0x32c
+#define RK3288_UOC0_CON3_UTMI_SUSPENDN			BIT(0)
+#define RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING		(1 << 1)
+#define RK3288_UOC0_CON3_UTMI_OPMODE_MASK		(3 << 1)
+#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC	(1 << 3)
+#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK		(3 << 3)
+#define RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED		BIT(5)
+#define RK3288_UOC0_CON3_BYPASSDMEN			BIT(6)
+#define RK3288_UOC0_CON3_BYPASSSEL			BIT(7)
+
+/*
+ * Enable the bypass of uart2 data through the otg usb phy.
+ * Original description in the TRM.
+ * 1. Disable the OTG block by setting OTGDISABLE0 to 1’b1.
+ * 2. Disable the pull-up resistance on the D+ line by setting
+ *    OPMODE0[1:0] to 2’b01.
+ * 3. To ensure that the XO, Bias, and PLL blocks are powered down in Suspend
+ *    mode, set COMMONONN to 1’b1.
+ * 4. Place the USB PHY in Suspend mode by setting SUSPENDM0 to 1’b0.
+ * 5. Set BYPASSSEL0 to 1’b1.
+ * 6. To transmit data, controls BYPASSDMEN0, and BYPASSDMDATA0.
+ * To receive data, monitor FSVPLUS0.
+ *
+ * The actual code in the vendor kernel does some things differently.
+ */
+static int __init rk3288_init_usb_uart(const struct rockchip_uart_data *data,
+				       struct regmap *grf)
+{
+	u32 val;
+	int ret;
+
+	/*
+	 * COMMON_ON and DISABLE settings are described in the TRM,
+	 * but were not present in the original code.
+	 * Also disable the analog phy components to save power.
+	 */
+	val = HIWORD_UPDATE(RK3288_UOC0_CON0_COMMON_ON_N
+				| RK3288_UOC0_CON0_DISABLE
+				| RK3288_UOC0_CON0_SIDDQ,
+			    RK3288_UOC0_CON0_COMMON_ON_N
+				| RK3288_UOC0_CON0_DISABLE
+				| RK3288_UOC0_CON0_SIDDQ);
+	ret = regmap_write(grf, RK3288_UOC0_CON0, val);
+	if (ret)
+		return ret;
+
+	val = HIWORD_UPDATE(RK3288_UOC0_CON2_SOFT_CON_SEL,
+			    RK3288_UOC0_CON2_SOFT_CON_SEL);
+	ret = regmap_write(grf, RK3288_UOC0_CON2, val);
+	if (ret)
+		return ret;
+
+	val = HIWORD_UPDATE(RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING
+				| RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC
+				| RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED,
+			    RK3288_UOC0_CON3_UTMI_SUSPENDN
+				| RK3288_UOC0_CON3_UTMI_OPMODE_MASK
+				| RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK
+				| RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED);
+	ret = regmap_write(grf, RK3288_UOC0_CON3, val);
+	if (ret)
+		return ret;
+
+	val = HIWORD_UPDATE(RK3288_UOC0_CON3_BYPASSSEL
+				| RK3288_UOC0_CON3_BYPASSDMEN,
+			    RK3288_UOC0_CON3_BYPASSSEL
+				| RK3288_UOC0_CON3_BYPASSDMEN);
+	ret = regmap_write(grf, RK3288_UOC0_CON3, val);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct rockchip_uart_data rk3288_uart_data __initconst = {
+	.grf_compatible = "rockchip,rk3288-grf",
+	.usb_compatible = "rockchip,rk3288-usb",
+	.usb_phys_addr = 0xff580000,
+	.init_uart = rk3288_init_usb_uart,
+};
+
+static const struct of_device_id rockchip_usb_uart_ids[] __initconst = {
+	{ .compatible = "rockchip,rk3288", .data = &rk3288_uart_data },
+	{ }
+};
+
+/*
+ * Find the usb controller using the shared usb-uart-phy in the dts and
+ * disable it.
+ */
+static int __init
+rockchip_disable_usb_controller(const struct rockchip_uart_data *data)
+{
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, data->usb_compatible) {
+		struct property *new_status;
+		struct resource res;
+		int ret;
+
+		ret = of_address_to_resource(np, 0, &res);
+		if (ret) {
+			pr_err("%s: could not get address of usb controller %s\n",
+			       __func__, np->full_name);
+			continue;
+		}
+
+		/* not the controller we're looking for */
+		if (res.start != data->usb_phys_addr)
+			continue;
+
+		pr_debug("%s: disabling usb controller %s\n",
+			 __func__, np->full_name);
+
+		new_status = kzalloc(sizeof(*new_status), GFP_KERNEL);
+		if (!new_status)
+			return -ENOMEM;
+
+		new_status->name = kstrdup("status", GFP_KERNEL);
+		new_status->length = sizeof("disabled");
+		new_status->value = kstrdup("disabled", GFP_KERNEL);
+
+		return of_update_property(np, new_status);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id __init *rockchip_usb_uart_data_lookup(void)
+{
+	struct device_node *root;
+	const struct of_device_id *id;
+
+	root = of_find_node_by_path("/");
+	if (!root)
+		return NULL;
+
+	id = of_match_node(rockchip_usb_uart_ids, root);
+	of_node_put(root);
+
+	return id;
+}
+
+static int __init rockchip_init_usb_uart(void)
+{
+	const struct of_device_id *match;
+	const struct rockchip_uart_data *data;
+	struct regmap *grf;
+	int ret;
+
+	if (!enable_usb_uart)
+		return 0;
+
+	match = rockchip_usb_uart_data_lookup();
+	if (!match)
+		return -ENOTSUPP;
+
+	pr_debug("%s: using settings for %s\n", __func__, match->compatible);
+	data = match->data;
+
+	grf = syscon_regmap_lookup_by_compatible(data->grf_compatible);
+	if (IS_ERR(grf)) {
+		pr_err("%s: could not find GRF syscon\n", __func__);
+		return PTR_ERR(grf);
+	}
+
+	ret = data->init_uart(data, grf);
+	if (ret) {
+		pr_err("%s: could not init usb_uart\n", __func__);
+		return ret;
+	}
+
+	return rockchip_disable_usb_controller(data);
+}
+early_initcall(rockchip_init_usb_uart);
+
+static int __init rockchip_usb_uart(char *buf)
+{
+	enable_usb_uart = true;
+	return 0;
+}
+early_param("rockchip.usb_uart", rockchip_usb_uart);
