From 276e58952f3f333bcd8dcca621d05499aa46ea6d Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Mon, 25 May 2015 16:38:07 +0200 Subject: [PATCH 3/4] UPSTREAM: soc/rockchip: add handler for usb-uart functionality Some Rockchip SoCs provide the possibility to use a usb-phy as passthru for the debug uart, making it possible to get console output without needing to open the device. This patch adds an early_initcall to enable this functionality conditionally and also disables the corresponding usb controller in the devicetree. Change-Id: I397df8f402c752125cf512332398757b91a899f8 Signed-off-by: Alexandru M Stan --- drivers/soc/Kconfig | 1 + drivers/soc/Makefile | 1 + drivers/soc/rockchip/Kconfig | 13 ++ drivers/soc/rockchip/Makefile | 1 + drivers/soc/rockchip/rockchip_usb_uart.c | 223 +++++++++++++++++++++++++++++++ 5 files changed, 239 insertions(+) create mode 100644 drivers/soc/rockchip/Kconfig create mode 100644 drivers/soc/rockchip/Makefile create mode 100644 drivers/soc/rockchip/rockchip_usb_uart.c diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index 1ee0b57..ad2f71a 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers" source "drivers/soc/img/Kconfig" source "drivers/soc/qcom/Kconfig" +source "drivers/soc/rockchip/Kconfig" source "drivers/soc/tegra/Kconfig" endmenu diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index bc43b87..d80386c 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -4,4 +4,5 @@ obj-$(CONFIG_SOC_IMG) += img/ obj-$(CONFIG_ARCH_QCOM) += qcom/ +obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ 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..97754f9 --- /dev/null +++ b/drivers/soc/rockchip/rockchip_usb_uart.c @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2015 Heiko Stuebner + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 = 0; + +#define RK3288_UOC0_CON0 0x320 +#define RK3288_UOC0_CON0_COMMON_ON_N BIT(0) +#define RK3288_UOC0_CON0_DISABLE BIT(4) + +#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; + + pr_info("%s\n", __func__); + + /* + * COMMON_ON and DISABLE settings are described in the TRM, + * but where not present in the original code. + */ + val = HIWORD_UPDATE(RK3288_UOC0_CON0_COMMON_ON_N + | RK3288_UOC0_CON0_DISABLE, + RK3288_UOC0_CON0_COMMON_ON_N + | RK3288_UOC0_CON0_DISABLE); + ret = regmap_write(grf, RK3288_UOC0_CON0, val); + if (ret) + return ret; + + // FIXME: this makes my system hang, for whatever reason + 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; +} + +const struct rockchip_uart_data rk3288_uart_data = { + .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[] = { + { .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_info("%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 *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_info("%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); -- 2.6.1