From affb42ab85d494403dd8782e78643fa4f3b83e4b Mon Sep 17 00:00:00 2001 From: Frank Wang Date: Fri, 17 Jan 2020 09:24:21 +0800 Subject: [PATCH] phy: rockchip: add a new driver for Rockchip USB3 PHY This implements the USB3.0 PHY driver for Rockchip platform with Inno IP block. Change-Id: I161915cf36fec441822f5f151f017ba8a7ecff9f Signed-off-by: Frank Wang --- drivers/phy/Kconfig | 6 + drivers/phy/Makefile | 1 + drivers/phy/phy-rockchip-inno-usb3.c | 574 +++++++++++++++++++++ include/linux/usb/phy-rockchip-inno-usb3.h | 18 + 4 files changed, 599 insertions(+) create mode 100644 drivers/phy/phy-rockchip-inno-usb3.c create mode 100644 include/linux/usb/phy-rockchip-inno-usb3.h diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index a5717a1e2a..6c92507add 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -65,6 +65,12 @@ config PHY_ROCKCHIP_INNO_USB2 help Support for Rockchip USB 2.0 PHY with Innosilicon IP block. +config PHY_ROCKCHIP_INNO_USB3 + bool "Support Rockchip INNO USB3PHY" + depends on PHY && ARCH_ROCKCHIP + help + Support for Rockchip USB 3.0 PHY with Innosilicon IP block. + config PIPE3_PHY bool "Support omap's PIPE3 PHY" depends on PHY && ARCH_OMAP2PLUS diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 2b8f47eee4..1f33777ec4 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -8,5 +8,6 @@ obj-$(CONFIG_$(SPL_)PHY) += phy-uclass.o obj-$(CONFIG_$(SPL_)NOP_PHY) += nop-phy.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB3) += phy-rockchip-inno-usb3.o obj-$(CONFIG_PHY_SANDBOX) += sandbox-phy.o obj-$(CONFIG_$(SPL_)PIPE3_PHY) += ti-pipe3-phy.o diff --git a/drivers/phy/phy-rockchip-inno-usb3.c b/drivers/phy/phy-rockchip-inno-usb3.c new file mode 100644 index 0000000000..235fbacd5d --- /dev/null +++ b/drivers/phy/phy-rockchip-inno-usb3.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Rockchip Electronics Co., Ltd + * + * Based on phy-rockchip-inno-usb3.c in Linux Kernel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define usleep_range(a, b) udelay((b)) + +#define U3PHY_PORT_NUM 2 +#define U3PHY_MAX_CLKS 4 +#define BIT_WRITEABLE_SHIFT 16 +#define SCHEDULE_DELAY (60 * HZ) + +#define U3PHY_APB_RST BIT(0) +#define U3PHY_POR_RST BIT(1) +#define U3PHY_MAC_RST BIT(2) + +struct rockchip_u3phy; +struct rockchip_u3phy_port; + +enum rockchip_u3phy_type { + U3PHY_TYPE_PIPE, + U3PHY_TYPE_UTMI, +}; + +enum rockchip_u3phy_pipe_pwr { + PIPE_PWR_P0 = 0, + PIPE_PWR_P1 = 1, + PIPE_PWR_P2 = 2, + PIPE_PWR_P3 = 3, + PIPE_PWR_MAX = 4, +}; + +enum rockchip_u3phy_rest_req { + U3_POR_RSTN = 0, + U2_POR_RSTN = 1, + PIPE_MAC_RSTN = 2, + UTMI_MAC_RSTN = 3, + PIPE_APB_RSTN = 4, + UTMI_APB_RSTN = 5, + U3PHY_RESET_MAX = 6, +}; + +enum rockchip_u3phy_utmi_state { + PHY_UTMI_HS_ONLINE = 0, + PHY_UTMI_DISCONNECT = 1, + PHY_UTMI_CONNECT = 2, + PHY_UTMI_FS_LS_ONLINE = 4, +}; + +/* + * @rvalue: reset value + * @dvalue: desired value + */ +struct u3phy_reg { + unsigned int offset; + unsigned int bitend; + unsigned int bitstart; + unsigned int rvalue; + unsigned int dvalue; +}; + +struct rockchip_u3phy_grfcfg { + struct u3phy_reg um_suspend; + struct u3phy_reg ls_det_en; + struct u3phy_reg ls_det_st; + struct u3phy_reg um_ls; + struct u3phy_reg um_hstdct; + struct u3phy_reg u2_only_ctrl; + struct u3phy_reg u3_disable; + struct u3phy_reg pp_pwr_st; + struct u3phy_reg pp_pwr_en[PIPE_PWR_MAX]; +}; + +/** + * struct rockchip_u3phy_apbcfg: usb3-phy apb configuration. + * @u2_pre_emp: usb2-phy pre-emphasis tuning. + * @u2_pre_emp_sth: usb2-phy pre-emphasis strength tuning. + * @u2_odt_tuning: usb2-phy odt 45ohm tuning. + */ +struct rockchip_u3phy_apbcfg { + unsigned int u2_pre_emp; + unsigned int u2_pre_emp_sth; + unsigned int u2_odt_tuning; +}; + +struct rockchip_u3phy_cfg { + unsigned int reg; + const struct rockchip_u3phy_grfcfg grfcfg; + + int (*phy_tuning)(struct rockchip_u3phy *u3phy, + struct rockchip_u3phy_port *u3phy_port, + const struct device_node *child_np); +}; + +struct rockchip_u3phy_port { + void __iomem *base; + unsigned int index; + unsigned char type; + bool refclk_25m_quirk; + struct mutex mutex; /* mutex for updating register */ +}; + +struct rockchip_u3phy { + struct udevice *dev; + struct regmap *u3phy_grf; + struct regmap *grf; + struct udevice *vbus_supply; + struct reset_ctl rsts[U3PHY_RESET_MAX]; + struct rockchip_u3phy_apbcfg apbcfg; + const struct rockchip_u3phy_cfg *cfgs; + struct rockchip_u3phy_port ports[U3PHY_PORT_NUM]; +}; + +static inline int param_write(void __iomem *base, + const struct u3phy_reg *reg, bool desired) +{ + unsigned int val, mask; + unsigned int tmp = desired ? reg->dvalue : reg->rvalue; + int ret = 0; + + mask = GENMASK(reg->bitend, reg->bitstart); + val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); + ret = regmap_write(base, reg->offset, val); + + return ret; +} + +static inline bool param_exped(void __iomem *base, + const struct u3phy_reg *reg, + unsigned int value) +{ + int ret; + unsigned int tmp, orig; + unsigned int mask = GENMASK(reg->bitend, reg->bitstart); + + ret = regmap_read(base, reg->offset, &orig); + if (ret) + return false; + + tmp = (orig & mask) >> reg->bitstart; + return tmp == value; +} + +int rockchip_u3phy_uboot_init(void) +{ + struct udevice *udev; + int ret; + + ret = uclass_get_device_by_name(UCLASS_PHY, "usb3-phy", &udev); + if (ret) + pr_err("%s: get usb3-phy node failed: %d\n", __func__, ret); + + (void)udev; + + return ret; +} + +static int rockchip_u3phy_init(struct phy *phy) +{ + return 0; +} + +static int rockchip_u3phy_exit(struct phy *phy) +{ + return 0; +} + +static int rockchip_u3phy_power_on(struct phy *phy) +{ + struct udevice *parent = dev_get_parent(phy->dev); + struct rockchip_u3phy *u3phy = dev_get_priv(parent); + int ret = 0; + + /* Vbus regulator */ + if (!u3phy->vbus_supply) { + ret = device_get_supply_regulator(parent, "vbus-supply", + &u3phy->vbus_supply); + if (ret == -ENOENT) { + pr_info("%s: Can't get VBus regulator!\n", __func__); + return 0; + } + + ret = regulator_set_enable(u3phy->vbus_supply, true); + if (ret) { + pr_err("%s: Failed to set VBus supply\n", __func__); + return ret; + } + } + + return 0; +} + +static int rockchip_u3phy_power_off(struct phy *phy) +{ + struct udevice *parent = dev_get_parent(phy->dev); + struct rockchip_u3phy *u3phy = dev_get_priv(parent); + int ret = 0; + + /* Turn off vbus regulator */ + if (u3phy->vbus_supply) { + ret = regulator_set_enable(u3phy->vbus_supply, false); + if (ret) { + pr_err("%s: Failed to set VBus supply\n", __func__); + return ret; + } + + u3phy->vbus_supply = NULL; + } + + return 0; +} + +static int rockchip_u3phy_bind(struct udevice *parent) +{ + struct udevice *dev; + ofnode node; + const char *name; + int ret; + + dev_for_each_subnode(node, parent) { + if (!ofnode_valid(node)) { + debug("%s: %s subnode not found", __func__, parent->name); + return -ENXIO; + } + + name = ofnode_get_name(node); + debug("%s: subnode %s\n", __func__, name); + + ret = device_bind_driver_to_node(parent, "rockchip_u3phy_port", + name, node, &dev); + if (ret) { + pr_err("%s: '%s' cannot bind 'rockchip_u3phy_port'\n", + __func__, name); + return ret; + } + } + + return 0; +} + +static const char *get_rest_name(enum rockchip_u3phy_rest_req rst) +{ + switch (rst) { + case U2_POR_RSTN: + return "u3phy-u2-por"; + case U3_POR_RSTN: + return "u3phy-u3-por"; + case PIPE_MAC_RSTN: + return "u3phy-pipe-mac"; + case UTMI_MAC_RSTN: + return "u3phy-utmi-mac"; + case UTMI_APB_RSTN: + return "u3phy-utmi-apb"; + case PIPE_APB_RSTN: + return "u3phy-pipe-apb"; + default: + return "invalid"; + } +} + +static void rockchip_u3phy_rest_deassert(struct rockchip_u3phy *u3phy, + unsigned int flag) +{ + int rst; + + if (flag & U3PHY_APB_RST) { + dev_dbg(u3phy->dev, "deassert APB bus interface reset\n"); + for (rst = PIPE_APB_RSTN; rst <= UTMI_APB_RSTN; rst++) { + if (u3phy->rsts[rst].dev) + reset_deassert(&u3phy->rsts[rst]); + } + } + + if (flag & U3PHY_POR_RST) { + usleep_range(12, 15); + dev_dbg(u3phy->dev, "deassert u2 and u3 phy power on reset\n"); + for (rst = U3_POR_RSTN; rst <= U2_POR_RSTN; rst++) { + if (u3phy->rsts[rst].dev) + reset_deassert(&u3phy->rsts[rst]); + } + } + + if (flag & U3PHY_MAC_RST) { + usleep_range(1200, 1500); + dev_dbg(u3phy->dev, "deassert pipe and utmi MAC reset\n"); + for (rst = PIPE_MAC_RSTN; rst <= UTMI_MAC_RSTN; rst++) + if (u3phy->rsts[rst].dev) + reset_deassert(&u3phy->rsts[rst]); + } +} + +static void rockchip_u3phy_rest_assert(struct rockchip_u3phy *u3phy) +{ + int rst; + + dev_dbg(u3phy->dev, "assert u3phy reset\n"); + for (rst = 0; rst < U3PHY_RESET_MAX; rst++) + if (u3phy->rsts[rst].dev) + reset_assert(&u3phy->rsts[rst]); +} + +static int rockchip_u3phy_parse_dt(struct rockchip_u3phy *u3phy, + struct udevice *udev) + +{ + int i, ret = 0; + + for (i = 0; i < U3PHY_RESET_MAX; i++) { + ret = reset_get_by_name(udev, get_rest_name(i), + &u3phy->rsts[i]); + if (ret) { + dev_info(udev, "no %s reset control specified\n", + get_rest_name(i)); + } + } + + return ret; +} + +static int rockchip_u3phy_port_init(struct rockchip_u3phy *u3phy, + struct rockchip_u3phy_port *u3phy_port, + const struct device_node *child_np) +{ + int ret; + + dev_dbg(u3phy->dev, "u3phy port initialize\n"); + + mutex_init(&u3phy_port->mutex); + + u3phy_port->base = (void __iomem *)ofnode_get_addr(np_to_ofnode(child_np)); + if (IS_ERR(u3phy_port->base)) { + dev_err(u3phy->dev, "failed to remap phy regs\n"); + return PTR_ERR(u3phy_port->base); + } + + if (!of_node_cmp(child_np->name, "pipe")) { + u3phy_port->type = U3PHY_TYPE_PIPE; + u3phy_port->refclk_25m_quirk = + ofnode_read_bool(np_to_ofnode(child_np), + "rockchip,refclk-25m-quirk"); + } else { + u3phy_port->type = U3PHY_TYPE_UTMI; + } + + if (u3phy->cfgs->phy_tuning) { + dev_dbg(u3phy->dev, "do u3phy tuning\n"); + ret = u3phy->cfgs->phy_tuning(u3phy, u3phy_port, child_np); + if (ret) + return ret; + } + + return 0; +} + +static int rockchip_u3phy_probe(struct udevice *udev) +{ + const struct udevice_id *of_match = udev->driver->of_match; + struct rockchip_u3phy *u3phy = dev_get_priv(udev); + const struct rockchip_u3phy_cfg *phy_cfgs; + ofnode child_np; + u32 reg[2], index; + int ret = 0; + + while (of_match->compatible) { + if (device_is_compatible(udev, of_match->compatible)) + break; + of_match++; + } + + if (!of_match || !of_match->data) { + dev_err(udev, "phy-cfgs are not assigned!\n"); + return -EINVAL; + } + + if (ofnode_read_u32_array(dev_ofnode(udev), "reg", reg, 2)) { + dev_err(udev, "could not read reg\n"); + return -EINVAL; + } + + u3phy->dev = udev; + phy_cfgs = (const struct rockchip_u3phy_cfg *)of_match->data; + + /* find out a proper config which can be matched with dt. */ + index = 0; + while (phy_cfgs[index].reg) { + if (phy_cfgs[index].reg == reg[1]) { + u3phy->cfgs = &phy_cfgs[index]; + break; + } + ++index; + } + + if (!u3phy->cfgs) { + dev_err(udev, "no phy-cfgs can be matched\n"); + return -EINVAL; + } + + ret = rockchip_u3phy_parse_dt(u3phy, udev); + if (ret) { + dev_err(udev, "parse dt failed, ret(%d)\n", ret); + return ret; + } + + rockchip_u3phy_rest_assert(u3phy); + rockchip_u3phy_rest_deassert(u3phy, U3PHY_APB_RST | U3PHY_POR_RST); + + index = 0; + ofnode_for_each_subnode(child_np, udev->node) { + struct rockchip_u3phy_port *u3phy_port = &u3phy->ports[index]; + + u3phy_port->index = index; + ret = rockchip_u3phy_port_init(u3phy, u3phy_port, + ofnode_to_np(child_np)); + if (ret) { + dev_err(udev, "u3phy port init failed,ret(%d)\n", ret); + goto put_child; + } + + /* to prevent out of boundary */ + if (++index >= U3PHY_PORT_NUM) + break; + } + + rockchip_u3phy_rest_deassert(u3phy, U3PHY_MAC_RST); + + dev_info(udev, "Rockchip u3phy initialized successfully\n"); + return 0; + +put_child: + of_node_put(ofnode_to_np(child_np)); + return ret; +} + +static int rk3328_u3phy_tuning(struct rockchip_u3phy *u3phy, + struct rockchip_u3phy_port *u3phy_port, + const struct device_node *child_np) +{ + if (u3phy_port->type == U3PHY_TYPE_UTMI) { + /* + * For rk3328 SoC, pre-emphasis and pre-emphasis strength must + * be written as one fixed value as below. + * + * Dissimilarly, the odt 45ohm value should be flexibly tuninged + * for the different boards to adjust HS eye height, so its + * value can be assigned in DT in code design. + */ + + /* {bits[2:0]=111}: always enable pre-emphasis */ + u3phy->apbcfg.u2_pre_emp = 0x0f; + + /* {bits[5:3]=000}: pre-emphasis strength as the weakest */ + u3phy->apbcfg.u2_pre_emp_sth = 0x41; + + /* {bits[4:0]=10101}: odt 45ohm tuning */ + u3phy->apbcfg.u2_odt_tuning = 0xb5; + + /* optional override of the odt 45ohm tuning */ + ofnode_read_u32(np_to_ofnode(child_np), + "rockchip,odt-val-tuning", + &u3phy->apbcfg.u2_odt_tuning); + + writel(u3phy->apbcfg.u2_pre_emp, u3phy_port->base + 0x030); + writel(u3phy->apbcfg.u2_pre_emp_sth, u3phy_port->base + 0x040); + writel(u3phy->apbcfg.u2_odt_tuning, u3phy_port->base + 0x11c); + } else if (u3phy_port->type == U3PHY_TYPE_PIPE) { + if (u3phy_port->refclk_25m_quirk) { + dev_dbg(u3phy->dev, "switch to 25m refclk\n"); + /* ref clk switch to 25M */ + writel(0x64, u3phy_port->base + 0x11c); + writel(0x64, u3phy_port->base + 0x028); + writel(0x01, u3phy_port->base + 0x020); + writel(0x21, u3phy_port->base + 0x030); + writel(0x06, u3phy_port->base + 0x108); + writel(0x00, u3phy_port->base + 0x118); + } else { + /* configure for 24M ref clk */ + writel(0x80, u3phy_port->base + 0x10c); + writel(0x01, u3phy_port->base + 0x118); + writel(0x38, u3phy_port->base + 0x11c); + writel(0x83, u3phy_port->base + 0x020); + writel(0x02, u3phy_port->base + 0x108); + } + + /* Enable SSC */ + udelay(3); + writel(0x08, u3phy_port->base + 0x000); + writel(0x0c, u3phy_port->base + 0x120); + + /* Tuning Rx for compliance RJTL test */ + writel(0x70, u3phy_port->base + 0x150); + writel(0x12, u3phy_port->base + 0x0c8); + writel(0x05, u3phy_port->base + 0x148); + writel(0x08, u3phy_port->base + 0x068); + writel(0xf0, u3phy_port->base + 0x1c4); + writel(0xff, u3phy_port->base + 0x070); + writel(0x0f, u3phy_port->base + 0x06c); + writel(0xe0, u3phy_port->base + 0x060); + + /* + * Tuning Tx to increase the bias current + * used in TX driver and RX EQ, it can + * also increase the voltage of LFPS. + */ + writel(0x08, u3phy_port->base + 0x180); + } else { + dev_err(u3phy->dev, "invalid u3phy port type\n"); + return -EINVAL; + } + + return 0; +} + +static struct phy_ops rockchip_u3phy_ops = { + .init = rockchip_u3phy_init, + .exit = rockchip_u3phy_exit, + .power_on= rockchip_u3phy_power_on, + .power_off= rockchip_u3phy_power_off, +}; + +static const struct rockchip_u3phy_cfg rk3328_u3phy_cfgs[] = { + { + .reg = 0xff470000, + .grfcfg = { + .um_suspend = { 0x0004, 15, 0, 0x1452, 0x15d1 }, + .u2_only_ctrl = { 0x0020, 15, 15, 0, 1 }, + .um_ls = { 0x0030, 5, 4, 0, 1 }, + .um_hstdct = { 0x0030, 7, 7, 0, 1 }, + .ls_det_en = { 0x0040, 0, 0, 0, 1 }, + .ls_det_st = { 0x0044, 0, 0, 0, 1 }, + .pp_pwr_st = { 0x0034, 14, 13, 0, 0}, + .pp_pwr_en = { {0x0020, 14, 0, 0x0014, 0x0005}, + {0x0020, 14, 0, 0x0014, 0x000d}, + {0x0020, 14, 0, 0x0014, 0x0015}, + {0x0020, 14, 0, 0x0014, 0x001d} }, + .u3_disable = { 0x04c4, 15, 0, 0x1100, 0x101}, + }, + .phy_tuning = rk3328_u3phy_tuning, + }, + { /* sentinel */ } +}; + +static const struct udevice_id rockchip_u3phy_dt_match[] = { + { .compatible = "rockchip,rk3328-u3phy", .data = (ulong)&rk3328_u3phy_cfgs }, + {} +}; + +U_BOOT_DRIVER(rockchip_u3phy_port) = { + .name = "rockchip_u3phy_port", + .id = UCLASS_PHY, + .ops = &rockchip_u3phy_ops, +}; + +U_BOOT_DRIVER(rockchip_u3phy) = { + .name = "rockchip_u3phy", + .id = UCLASS_PHY, + .of_match = rockchip_u3phy_dt_match, + .probe = rockchip_u3phy_probe, + .bind = rockchip_u3phy_bind, + .priv_auto_alloc_size = sizeof(struct rockchip_u3phy), +}; diff --git a/include/linux/usb/phy-rockchip-inno-usb3.h b/include/linux/usb/phy-rockchip-inno-usb3.h new file mode 100644 index 0000000000..7925857ccc --- /dev/null +++ b/include/linux/usb/phy-rockchip-inno-usb3.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Rockchip Electronics Co., Ltd + */ + +#ifndef _PHY_ROCKCHIP_INNO_USB3_H +#define _PHY_ROCKCHIP_INNO_USB3_H + +#if CONFIG_IS_ENABLED(PHY_ROCKCHIP_INNO_USB3) +int rockchip_u3phy_uboot_init(void); +#else +static inline int rockchip_u3phy_uboot_init(void) +{ + return -ENOTSUPP; +} +#endif + +#endif /* _PHY_ROCKCHIP_INNO_USB3_H */