diff --git a/config/kernel/linux-rockchip64-current.config b/config/kernel/linux-rockchip64-current.config index da1a236d10..74c2e7ff1e 100644 --- a/config/kernel/linux-rockchip64-current.config +++ b/config/kernel/linux-rockchip64-current.config @@ -1,6 +1,6 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/arm64 5.4.6 Kernel Configuration +# Linux/arm64 5.4.13 Kernel Configuration # # @@ -657,6 +657,7 @@ CONFIG_ARCH_MMAP_RND_BITS=18 CONFIG_HAVE_ARCH_MMAP_RND_COMPAT_BITS=y CONFIG_ARCH_MMAP_RND_COMPAT_BITS=11 CONFIG_ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT=y +CONFIG_HAVE_COPY_THREAD_TLS=y CONFIG_CLONE_BACKWARDS=y CONFIG_OLD_SIGSUSPEND3=y CONFIG_COMPAT_OLD_SIGACTION=y @@ -1795,6 +1796,7 @@ CONFIG_OF_CONFIGFS=y # CONFIG_PARPORT is not set CONFIG_BLK_DEV=y # CONFIG_BLK_DEV_NULL_BLK is not set +CONFIG_CDROM=m # CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set CONFIG_ZRAM=m CONFIG_ZRAM_WRITEBACK=y @@ -3335,6 +3337,7 @@ CONFIG_BATTERY_BQ27XXX_HDQ=m # CONFIG_BATTERY_MAX17040 is not set # CONFIG_BATTERY_MAX17042 is not set CONFIG_BATTERY_MAX1721X=m +CONFIG_CHARGER_ISP1704=m # CONFIG_CHARGER_MAX8903 is not set # CONFIG_CHARGER_LP8727 is not set # CONFIG_CHARGER_GPIO is not set @@ -5316,6 +5319,7 @@ CONFIG_USB_CHAOSKEY=m # # USB Physical Layer drivers # +CONFIG_USB_PHY=y # CONFIG_NOP_USB_XCEIV is not set # CONFIG_USB_GPIO_VBUS is not set # CONFIG_USB_ISP1301 is not set @@ -6531,6 +6535,7 @@ CONFIG_PHY_ROCKCHIP_DP=m CONFIG_PHY_ROCKCHIP_EMMC=y CONFIG_PHY_ROCKCHIP_INNO_HDMI=m CONFIG_PHY_ROCKCHIP_INNO_USB2=y +CONFIG_PHY_ROCKCHIP_INNO_USB3=m CONFIG_PHY_ROCKCHIP_PCIE=m CONFIG_PHY_ROCKCHIP_TYPEC=m CONFIG_PHY_ROCKCHIP_USB=m diff --git a/patch/kernel/rockchip64-current/add-rk3328-usb3-phy-driver.patch b/patch/kernel/rockchip64-current/add-rk3328-usb3-phy-driver.patch new file mode 100755 index 0000000000..7ac1a2e432 --- /dev/null +++ b/patch/kernel/rockchip64-current/add-rk3328-usb3-phy-driver.patch @@ -0,0 +1,1504 @@ +Add the rockchip innosilicon usb3 phy driver, supporting devices such as the rk3328. +Pulled from: +https://github.com/FireflyTeam/kernel/blob/roc-rk3328-cc/drivers/phy/rockchip/phy-rockchip-inno-usb3.c + +Signed-off-by: Peter Geis +--- + drivers/phy/rockchip/Kconfig | 9 + + drivers/phy/rockchip/Makefile | 1 + + drivers/phy/rockchip/phy-rockchip-inno-usb3.c | 1107 +++++++++++++++++ + 3 files changed, 1117 insertions(+) + create mode 100644 drivers/phy/rockchip/phy-rockchip-inno-usb3.c + +diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig +index c454c90cd99e..766407939d4a 100644 +--- a/drivers/phy/rockchip/Kconfig ++++ b/drivers/phy/rockchip/Kconfig +@@ -35,6 +35,15 @@ config PHY_ROCKCHIP_INNO_USB2 + help + Support for Rockchip USB2.0 PHY with Innosilicon IP block. + ++config PHY_ROCKCHIP_INNO_USB3 ++ tristate "Rockchip INNO USB 3.0 PHY Driver" ++ depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF ++ depends on USB_SUPPORT ++ select GENERIC_PHY ++ select USB_PHY ++ help ++ Support for Rockchip USB 3.0 PHY with Innosilicon IP block. ++ + config PHY_ROCKCHIP_PCIE + tristate "Rockchip PCIe PHY Driver" + depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST +diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile +index fd21cbaf40dd..d7b3d16c19ae 100644 +--- a/drivers/phy/rockchip/Makefile ++++ b/drivers/phy/rockchip/Makefile +@@ -3,6 +3,7 @@ obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o + obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o + obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.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_ROCKCHIP_PCIE) += phy-rockchip-pcie.o + obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o + obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o +diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb3.c b/drivers/phy/rockchip/phy-rockchip-inno-usb3.c +new file mode 100644 +index 000000000000..31fee8f3a705 +--- /dev/null ++++ b/drivers/phy/rockchip/phy-rockchip-inno-usb3.c +@@ -0,0 +1,1107 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Rockchip USB 3.0 PHY with Innosilicon IP block driver ++ * ++ * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define U3PHY_PORT_NUM 2 ++#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_pipe_power)(struct rockchip_u3phy *u3phy, ++ struct rockchip_u3phy_port *u3phy_port, ++ bool on); ++ int (*phy_tuning)(struct rockchip_u3phy *u3phy, ++ struct rockchip_u3phy_port *u3phy_port, ++ struct device_node *child_np); ++}; ++ ++struct rockchip_u3phy_port { ++ struct phy *phy; ++ void __iomem *base; ++ unsigned int index; ++ unsigned char type; ++ bool suspended; ++ bool refclk_25m_quirk; ++ struct mutex mutex; /* mutex for updating register */ ++ struct delayed_work um_sm_work; ++}; ++ ++struct rockchip_u3phy { ++ struct device *dev; ++ struct regmap *u3phy_grf; ++ struct regmap *grf; ++ int um_ls_irq; ++ struct clk **clks; ++ int num_clocks; ++ struct dentry *root; ++ struct gpio_desc *vbus_drv_gpio; ++ struct reset_control *rsts[U3PHY_RESET_MAX]; ++ struct rockchip_u3phy_apbcfg apbcfg; ++ const struct rockchip_u3phy_cfg *cfgs; ++ struct rockchip_u3phy_port ports[U3PHY_PORT_NUM]; ++ struct usb_phy usb_phy; ++}; ++ ++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; ++} ++ ++static int rockchip_u3phy_usb2_only_show(struct seq_file *s, void *unused) ++{ ++ struct rockchip_u3phy *u3phy = s->private; ++ ++ if (param_exped(u3phy->u3phy_grf, &u3phy->cfgs->grfcfg.u2_only_ctrl, 1)) ++ dev_info(u3phy->dev, "u2\n"); ++ else ++ dev_info(u3phy->dev, "u3\n"); ++ ++ return 0; ++} ++ ++static int rockchip_u3phy_usb2_only_open(struct inode *inode, ++ struct file *file) ++{ ++ return single_open(file, rockchip_u3phy_usb2_only_show, ++ inode->i_private); ++} ++ ++static ssize_t rockchip_u3phy_usb2_only_write(struct file *file, ++ const char __user *ubuf, ++ size_t count, loff_t *ppos) ++{ ++ struct seq_file *s = file->private_data; ++ struct rockchip_u3phy *u3phy = s->private; ++ struct rockchip_u3phy_port *u3phy_port; ++ char buf[32]; ++ u8 index; ++ ++ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) ++ return -EFAULT; ++ ++ if (!strncmp(buf, "u3", 2) && ++ param_exped(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.u2_only_ctrl, 1)) { ++ dev_info(u3phy->dev, "Set usb3.0 and usb2.0 mode successfully\n"); ++ ++ gpiod_set_value_cansleep(u3phy->vbus_drv_gpio, 0); ++ ++ param_write(u3phy->grf, ++ &u3phy->cfgs->grfcfg.u3_disable, false); ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.u2_only_ctrl, false); ++ ++ for (index = 0; index < U3PHY_PORT_NUM; index++) { ++ u3phy_port = &u3phy->ports[index]; ++ /* enable u3 rx termimation */ ++ if (u3phy_port->type == U3PHY_TYPE_PIPE) ++ writel(0x30, u3phy_port->base + 0xd8); ++ } ++ ++ atomic_notifier_call_chain(&u3phy->usb_phy.notifier, 0, NULL); ++ ++ gpiod_set_value_cansleep(u3phy->vbus_drv_gpio, 1); ++ } else if (!strncmp(buf, "u2", 2) && ++ param_exped(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.u2_only_ctrl, 0)) { ++ dev_info(u3phy->dev, "Set usb2.0 only mode successfully\n"); ++ ++ gpiod_set_value_cansleep(u3phy->vbus_drv_gpio, 0); ++ ++ param_write(u3phy->grf, ++ &u3phy->cfgs->grfcfg.u3_disable, true); ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.u2_only_ctrl, true); ++ ++ for (index = 0; index < U3PHY_PORT_NUM; index++) { ++ u3phy_port = &u3phy->ports[index]; ++ /* disable u3 rx termimation */ ++ if (u3phy_port->type == U3PHY_TYPE_PIPE) ++ writel(0x20, u3phy_port->base + 0xd8); ++ } ++ ++ atomic_notifier_call_chain(&u3phy->usb_phy.notifier, 0, NULL); ++ ++ gpiod_set_value_cansleep(u3phy->vbus_drv_gpio, 1); ++ } else { ++ dev_info(u3phy->dev, "Same or illegal mode\n"); ++ } ++ ++ return count; ++} ++ ++static const struct file_operations rockchip_u3phy_usb2_only_fops = { ++ .open = rockchip_u3phy_usb2_only_open, ++ .write = rockchip_u3phy_usb2_only_write, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = single_release, ++}; ++ ++int rockchip_u3phy_debugfs_init(struct rockchip_u3phy *u3phy) ++{ ++ struct dentry *root; ++ struct dentry *file; ++ int ret; ++ ++ root = debugfs_create_dir(dev_name(u3phy->dev), NULL); ++ if (!root) { ++ ret = -ENOMEM; ++ goto err0; ++ } ++ ++ u3phy->root = root; ++ ++ file = debugfs_create_file("u3phy_mode", 0644, root, ++ u3phy, &rockchip_u3phy_usb2_only_fops); ++ if (!file) { ++ ret = -ENOMEM; ++ goto err1; ++ } ++ return 0; ++ ++err1: ++ debugfs_remove_recursive(root); ++err0: ++ return ret; ++} ++ ++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]) ++ reset_control_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]) ++ reset_control_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]) ++ reset_control_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]) ++ reset_control_assert(u3phy->rsts[rst]); ++} ++ ++static int rockchip_u3phy_clk_enable(struct rockchip_u3phy *u3phy) ++{ ++ int ret, clk; ++ ++ for (clk = 0; clk < u3phy->num_clocks && u3phy->clks[clk]; clk++) { ++ ret = clk_prepare_enable(u3phy->clks[clk]); ++ if (ret) ++ goto err_disable_clks; ++ } ++ return 0; ++ ++err_disable_clks: ++ while (--clk >= 0) ++ clk_disable_unprepare(u3phy->clks[clk]); ++ return ret; ++} ++ ++static void rockchip_u3phy_clk_disable(struct rockchip_u3phy *u3phy) ++{ ++ int clk; ++ ++ for (clk = u3phy->num_clocks - 1; clk >= 0; clk--) ++ if (u3phy->clks[clk]) ++ clk_disable_unprepare(u3phy->clks[clk]); ++} ++ ++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 rockchip_u3phy_port *u3phy_port = phy_get_drvdata(phy); ++ struct rockchip_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); ++ int ret; ++ ++ dev_info(&u3phy_port->phy->dev, "u3phy %s power on\n", ++ (u3phy_port->type == U3PHY_TYPE_UTMI) ? "u2" : "u3"); ++ ++ if (!u3phy_port->suspended) ++ return 0; ++ ++ ret = rockchip_u3phy_clk_enable(u3phy); ++ if (ret) ++ return ret; ++ ++ if (u3phy_port->type == U3PHY_TYPE_UTMI) { ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.um_suspend, false); ++ } else { ++ /* current in p2 ? */ ++ if (param_exped(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.pp_pwr_st, PIPE_PWR_P2)) ++ goto done; ++ ++ if (u3phy->cfgs->phy_pipe_power) { ++ dev_dbg(u3phy->dev, "do pipe power up\n"); ++ u3phy->cfgs->phy_pipe_power(u3phy, u3phy_port, true); ++ } ++ ++ /* exit to p0 */ ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.pp_pwr_en[PIPE_PWR_P0], true); ++ usleep_range(90, 100); ++ ++ /* enter to p2 from p0 */ ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.pp_pwr_en[PIPE_PWR_P2], ++ false); ++ udelay(3); ++ } ++ ++done: ++ u3phy_port->suspended = false; ++ return 0; ++} ++ ++static int rockchip_u3phy_power_off(struct phy *phy) ++{ ++ struct rockchip_u3phy_port *u3phy_port = phy_get_drvdata(phy); ++ struct rockchip_u3phy *u3phy = dev_get_drvdata(phy->dev.parent); ++ ++ dev_info(&u3phy_port->phy->dev, "u3phy %s power off\n", ++ (u3phy_port->type == U3PHY_TYPE_UTMI) ? "u2" : "u3"); ++ ++ if (u3phy_port->suspended) ++ return 0; ++ ++ if (u3phy_port->type == U3PHY_TYPE_UTMI) { ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.um_suspend, true); ++ } else { ++ /* current in p3 ? */ ++ if (param_exped(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.pp_pwr_st, PIPE_PWR_P3)) ++ goto done; ++ ++ /* exit to p0 */ ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.pp_pwr_en[PIPE_PWR_P0], true); ++ udelay(2); ++ ++ /* enter to p3 from p0 */ ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.pp_pwr_en[PIPE_PWR_P3], true); ++ udelay(6); ++ ++ if (u3phy->cfgs->phy_pipe_power) { ++ dev_dbg(u3phy->dev, "do pipe power down\n"); ++ u3phy->cfgs->phy_pipe_power(u3phy, u3phy_port, false); ++ } ++ } ++ ++done: ++ rockchip_u3phy_clk_disable(u3phy); ++ u3phy_port->suspended = true; ++ return 0; ++} ++ ++static __maybe_unused ++struct phy *rockchip_u3phy_xlate(struct device *dev, ++ struct of_phandle_args *args) ++{ ++ struct rockchip_u3phy *u3phy = dev_get_drvdata(dev); ++ struct rockchip_u3phy_port *u3phy_port = NULL; ++ struct device_node *phy_np = args->np; ++ int index; ++ ++ if (args->args_count != 1) { ++ dev_err(dev, "invalid number of cells in 'phy' property\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ for (index = 0; index < U3PHY_PORT_NUM; index++) { ++ if (phy_np == u3phy->ports[index].phy->dev.of_node) { ++ u3phy_port = &u3phy->ports[index]; ++ break; ++ } ++ } ++ ++ if (!u3phy_port) { ++ dev_err(dev, "failed to find appropriate phy\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return u3phy_port->phy; ++} ++ ++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, ++ .owner = THIS_MODULE, ++}; ++ ++/* ++ * The function manage host-phy port state and suspend/resume phy port ++ * to save power automatically. ++ * ++ * we rely on utmi_linestate and utmi_hostdisconnect to identify whether ++ * devices is disconnect or not. Besides, we do not need care it is FS/LS ++ * disconnected or HS disconnected, actually, we just only need get the ++ * device is disconnected at last through rearm the delayed work, ++ * to suspend the phy port in _PHY_STATE_DISCONNECT_ case. ++ */ ++static void rockchip_u3phy_um_sm_work(struct work_struct *work) ++{ ++ struct rockchip_u3phy_port *u3phy_port = ++ container_of(work, struct rockchip_u3phy_port, um_sm_work.work); ++ struct rockchip_u3phy *u3phy = ++ dev_get_drvdata(u3phy_port->phy->dev.parent); ++ unsigned int sh = u3phy->cfgs->grfcfg.um_hstdct.bitend - ++ u3phy->cfgs->grfcfg.um_hstdct.bitstart + 1; ++ unsigned int ul, uhd, state; ++ unsigned int ul_mask, uhd_mask; ++ int ret; ++ ++ mutex_lock(&u3phy_port->mutex); ++ ++ ret = regmap_read(u3phy->u3phy_grf, ++ u3phy->cfgs->grfcfg.um_ls.offset, &ul); ++ if (ret < 0) ++ goto next_schedule; ++ ++ ret = regmap_read(u3phy->u3phy_grf, ++ u3phy->cfgs->grfcfg.um_hstdct.offset, &uhd); ++ if (ret < 0) ++ goto next_schedule; ++ ++ uhd_mask = GENMASK(u3phy->cfgs->grfcfg.um_hstdct.bitend, ++ u3phy->cfgs->grfcfg.um_hstdct.bitstart); ++ ul_mask = GENMASK(u3phy->cfgs->grfcfg.um_ls.bitend, ++ u3phy->cfgs->grfcfg.um_ls.bitstart); ++ ++ /* stitch on um_ls and um_hstdct as phy state */ ++ state = ((uhd & uhd_mask) >> u3phy->cfgs->grfcfg.um_hstdct.bitstart) | ++ (((ul & ul_mask) >> u3phy->cfgs->grfcfg.um_ls.bitstart) << sh); ++ ++ switch (state) { ++ case PHY_UTMI_HS_ONLINE: ++ dev_dbg(&u3phy_port->phy->dev, "HS online\n"); ++ break; ++ case PHY_UTMI_FS_LS_ONLINE: ++ /* ++ * For FS/LS device, the online state share with connect state ++ * from um_ls and um_hstdct register, so we distinguish ++ * them via suspended flag. ++ * ++ * Plus, there are two cases, one is D- Line pull-up, and D+ ++ * line pull-down, the state is 4; another is D+ line pull-up, ++ * and D- line pull-down, the state is 2. ++ */ ++ if (!u3phy_port->suspended) { ++ /* D- line pull-up, D+ line pull-down */ ++ dev_dbg(&u3phy_port->phy->dev, "FS/LS online\n"); ++ break; ++ } ++ /* fall through */ ++ case PHY_UTMI_CONNECT: ++ if (u3phy_port->suspended) { ++ dev_dbg(&u3phy_port->phy->dev, "Connected\n"); ++ rockchip_u3phy_power_on(u3phy_port->phy); ++ u3phy_port->suspended = false; ++ } else { ++ /* D+ line pull-up, D- line pull-down */ ++ dev_dbg(&u3phy_port->phy->dev, "FS/LS online\n"); ++ } ++ break; ++ case PHY_UTMI_DISCONNECT: ++ if (!u3phy_port->suspended) { ++ dev_dbg(&u3phy_port->phy->dev, "Disconnected\n"); ++ rockchip_u3phy_power_off(u3phy_port->phy); ++ u3phy_port->suspended = true; ++ } ++ ++ /* ++ * activate the linestate detection to get the next device ++ * plug-in irq. ++ */ ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.ls_det_st, true); ++ param_write(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.ls_det_en, true); ++ ++ /* ++ * we don't need to rearm the delayed work when the phy port ++ * is suspended. ++ */ ++ mutex_unlock(&u3phy_port->mutex); ++ return; ++ default: ++ dev_dbg(&u3phy_port->phy->dev, "unknown phy state\n"); ++ break; ++ } ++ ++next_schedule: ++ mutex_unlock(&u3phy_port->mutex); ++ schedule_delayed_work(&u3phy_port->um_sm_work, SCHEDULE_DELAY); ++} ++ ++static irqreturn_t rockchip_u3phy_um_ls_irq(int irq, void *data) ++{ ++ struct rockchip_u3phy_port *u3phy_port = data; ++ struct rockchip_u3phy *u3phy = ++ dev_get_drvdata(u3phy_port->phy->dev.parent); ++ ++ if (!param_exped(u3phy->u3phy_grf, ++ &u3phy->cfgs->grfcfg.ls_det_st, ++ u3phy->cfgs->grfcfg.ls_det_st.dvalue)) ++ return IRQ_NONE; ++ ++ dev_dbg(u3phy->dev, "utmi linestate interrupt\n"); ++ mutex_lock(&u3phy_port->mutex); ++ ++ /* disable linestate detect irq and clear its status */ ++ param_write(u3phy->u3phy_grf, &u3phy->cfgs->grfcfg.ls_det_en, false); ++ param_write(u3phy->u3phy_grf, &u3phy->cfgs->grfcfg.ls_det_st, true); ++ ++ mutex_unlock(&u3phy_port->mutex); ++ ++ /* ++ * In this case for host phy, a new device is plugged in, meanwhile, ++ * if the phy port is suspended, we need rearm the work to resume it ++ * and mange its states; otherwise, we just return irq handled. ++ */ ++ if (u3phy_port->suspended) { ++ dev_dbg(u3phy->dev, "schedule utmi sm work\n"); ++ rockchip_u3phy_um_sm_work(&u3phy_port->um_sm_work.work); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static int rockchip_u3phy_parse_dt(struct rockchip_u3phy *u3phy, ++ struct platform_device *pdev) ++ ++{ ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; ++ int ret, i, clk; ++ ++ u3phy->um_ls_irq = platform_get_irq_byname(pdev, "linestate"); ++ if (u3phy->um_ls_irq < 0) { ++ dev_err(dev, "get utmi linestate irq failed\n"); ++ return -ENXIO; ++ } ++ ++ u3phy->vbus_drv_gpio = devm_gpiod_get_optional(dev, "vbus-drv", ++ GPIOD_OUT_HIGH); ++ ++ if (!u3phy->vbus_drv_gpio) { ++ dev_warn(&pdev->dev, "vbus_drv is not assigned\n"); ++ } else if (IS_ERR(u3phy->vbus_drv_gpio)) { ++ dev_err(&pdev->dev, "failed to get vbus_drv\n"); ++ return PTR_ERR(u3phy->vbus_drv_gpio); ++ } ++ ++ u3phy->num_clocks = of_clk_get_parent_count(np); ++ if (u3phy->num_clocks == 0) ++ dev_warn(&pdev->dev, "no clks found in dt\n"); ++ ++ u3phy->clks = devm_kcalloc(dev, u3phy->num_clocks, ++ sizeof(struct clk *), GFP_KERNEL); ++ ++ for (clk = 0; clk < u3phy->num_clocks; clk++) { ++ u3phy->clks[clk] = of_clk_get(np, clk); ++ if (IS_ERR(u3phy->clks[clk])) { ++ ret = PTR_ERR(u3phy->clks[clk]); ++ if (ret == -EPROBE_DEFER) ++ goto err_put_clks; ++ dev_err(&pdev->dev, "failed to get clks, %i\n", ++ ret); ++ u3phy->clks[clk] = NULL; ++ break; ++ } ++ } ++ ++ for (i = 0; i < U3PHY_RESET_MAX; i++) { ++ u3phy->rsts[i] = devm_reset_control_get(dev, get_rest_name(i)); ++ if (IS_ERR(u3phy->rsts[i])) { ++ dev_info(dev, "no %s reset control specified\n", ++ get_rest_name(i)); ++ u3phy->rsts[i] = NULL; ++ } ++ } ++ ++ return 0; ++ ++err_put_clks: ++ while (--clk >= 0) ++ clk_put(u3phy->clks[clk]); ++ return ret; ++} ++ ++static int rockchip_u3phy_port_init(struct rockchip_u3phy *u3phy, ++ struct rockchip_u3phy_port *u3phy_port, ++ struct device_node *child_np) ++{ ++ struct resource res; ++ struct phy *phy; ++ int ret; ++ ++ dev_dbg(u3phy->dev, "u3phy port initialize\n"); ++ ++ mutex_init(&u3phy_port->mutex); ++ u3phy_port->suspended = true; /* initial status */ ++ ++ phy = devm_phy_create(u3phy->dev, child_np, &rockchip_u3phy_ops); ++ if (IS_ERR(phy)) { ++ dev_err(u3phy->dev, "failed to create phy\n"); ++ return PTR_ERR(phy); ++ } ++ ++ u3phy_port->phy = phy; ++ ++ ret = of_address_to_resource(child_np, 0, &res); ++ if (ret) { ++ dev_err(u3phy->dev, "failed to get address resource(np-%s)\n", ++ child_np->name); ++ return ret; ++ } ++ ++ u3phy_port->base = devm_ioremap_resource(&u3phy_port->phy->dev, &res); ++ 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 = ++ of_property_read_bool(child_np, ++ "rockchip,refclk-25m-quirk"); ++ } else { ++ u3phy_port->type = U3PHY_TYPE_UTMI; ++ INIT_DELAYED_WORK(&u3phy_port->um_sm_work, ++ rockchip_u3phy_um_sm_work); ++ ++ ret = devm_request_threaded_irq(u3phy->dev, u3phy->um_ls_irq, ++ NULL, rockchip_u3phy_um_ls_irq, ++ IRQF_ONESHOT, "rockchip_u3phy", ++ u3phy_port); ++ if (ret) { ++ dev_err(u3phy->dev, "failed to request utmi linestate irq handle\n"); ++ return ret; ++ } ++ } ++ ++ 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; ++ } ++ ++ phy_set_drvdata(u3phy_port->phy, u3phy_port); ++ return 0; ++} ++ ++static int rockchip_u3phy_on_init(struct usb_phy *usb_phy) ++{ ++ struct rockchip_u3phy *u3phy = ++ container_of(usb_phy, struct rockchip_u3phy, usb_phy); ++ ++ rockchip_u3phy_rest_deassert(u3phy, U3PHY_POR_RST | U3PHY_MAC_RST); ++ return 0; ++} ++ ++static void rockchip_u3phy_on_shutdown(struct usb_phy *usb_phy) ++{ ++ struct rockchip_u3phy *u3phy = ++ container_of(usb_phy, struct rockchip_u3phy, usb_phy); ++ int rst; ++ ++ for (rst = 0; rst < U3PHY_RESET_MAX; rst++) ++ if (u3phy->rsts[rst] && rst != UTMI_APB_RSTN && ++ rst != PIPE_APB_RSTN) ++ reset_control_assert(u3phy->rsts[rst]); ++ udelay(1); ++} ++ ++static int rockchip_u3phy_on_disconnect(struct usb_phy *usb_phy, ++ enum usb_device_speed speed) ++{ ++ struct rockchip_u3phy *u3phy = ++ container_of(usb_phy, struct rockchip_u3phy, usb_phy); ++ ++ dev_info(u3phy->dev, "%s device has disconnected\n", ++ (speed == USB_SPEED_SUPER) ? "U3" : "UW/U2/U1.1/U1"); ++ ++ if (speed == USB_SPEED_SUPER) ++ atomic_notifier_call_chain(&usb_phy->notifier, 0, NULL); ++ ++ return 0; ++} ++ ++static int rockchip_u3phy_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; ++ struct device_node *child_np; ++ struct phy_provider *provider; ++ struct rockchip_u3phy *u3phy; ++ const struct rockchip_u3phy_cfg *phy_cfgs; ++ const struct of_device_id *match; ++ unsigned int reg[2]; ++ int index, ret; ++ ++ match = of_match_device(dev->driver->of_match_table, dev); ++ if (!match || !match->data) { ++ dev_err(dev, "phy-cfgs are not assigned!\n"); ++ return -EINVAL; ++ } ++ ++ u3phy = devm_kzalloc(dev, sizeof(*u3phy), GFP_KERNEL); ++ if (!u3phy) ++ return -ENOMEM; ++ ++ u3phy->u3phy_grf = ++ syscon_regmap_lookup_by_phandle(np, "rockchip,u3phygrf"); ++ if (IS_ERR(u3phy->u3phy_grf)) ++ return PTR_ERR(u3phy->u3phy_grf); ++ ++ u3phy->grf = ++ syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); ++ if (IS_ERR(u3phy->grf)) { ++ dev_err(dev, "Missing rockchip,grf property\n"); ++ return PTR_ERR(u3phy->grf); ++ } ++ ++ if (of_property_read_u32_array(np, "reg", reg, 2)) { ++ dev_err(dev, "the reg property is not assigned in %s node\n", ++ np->name); ++ return -EINVAL; ++ } ++ ++ u3phy->dev = dev; ++ phy_cfgs = match->data; ++ platform_set_drvdata(pdev, u3phy); ++ ++ /* 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(dev, "no phy-cfgs can be matched with %s node\n", ++ np->name); ++ return -EINVAL; ++ } ++ ++ ret = rockchip_u3phy_parse_dt(u3phy, pdev); ++ if (ret) { ++ dev_err(dev, "parse dt failed, ret(%d)\n", ret); ++ return ret; ++ } ++ ++ ret = rockchip_u3phy_clk_enable(u3phy); ++ if (ret) { ++ dev_err(dev, "clk enable 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; ++ for_each_available_child_of_node(np, child_np) { ++ struct rockchip_u3phy_port *u3phy_port = &u3phy->ports[index]; ++ ++ u3phy_port->index = index; ++ ret = rockchip_u3phy_port_init(u3phy, u3phy_port, child_np); ++ if (ret) { ++ dev_err(dev, "u3phy port init failed,ret(%d)\n", ret); ++ goto put_child; ++ } ++ ++ /* to prevent out of boundary */ ++ if (++index >= U3PHY_PORT_NUM) ++ break; ++ } ++ ++ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); ++ if (IS_ERR_OR_NULL(provider)) ++ goto put_child; ++ ++ rockchip_u3phy_rest_deassert(u3phy, U3PHY_MAC_RST); ++ rockchip_u3phy_clk_disable(u3phy); ++ ++ u3phy->usb_phy.dev = dev; ++ u3phy->usb_phy.init = rockchip_u3phy_on_init; ++ u3phy->usb_phy.shutdown = rockchip_u3phy_on_shutdown; ++ u3phy->usb_phy.notify_disconnect = rockchip_u3phy_on_disconnect; ++ usb_add_phy(&u3phy->usb_phy, USB_PHY_TYPE_USB3); ++ ATOMIC_INIT_NOTIFIER_HEAD(&u3phy->usb_phy.notifier); ++ ++ rockchip_u3phy_debugfs_init(u3phy); ++ ++ dev_info(dev, "Rockchip u3phy initialized successfully\n"); ++ return 0; ++ ++put_child: ++ of_node_put(child_np); ++ return ret; ++} ++ ++static int rk3328_u3phy_pipe_power(struct rockchip_u3phy *u3phy, ++ struct rockchip_u3phy_port *u3phy_port, ++ bool on) ++{ ++ unsigned int reg; ++ ++ if (on) { ++ reg = readl(u3phy_port->base + 0x1a8); ++ reg &= ~BIT(4); /* ldo power up */ ++ writel(reg, u3phy_port->base + 0x1a8); ++ ++ reg = readl(u3phy_port->base + 0x044); ++ reg &= ~BIT(4); /* bg power on */ ++ writel(reg, u3phy_port->base + 0x044); ++ ++ reg = readl(u3phy_port->base + 0x150); ++ reg |= BIT(6); /* tx bias enable */ ++ writel(reg, u3phy_port->base + 0x150); ++ ++ reg = readl(u3phy_port->base + 0x080); ++ reg &= ~BIT(2); /* tx cm power up */ ++ writel(reg, u3phy_port->base + 0x080); ++ ++ reg = readl(u3phy_port->base + 0x0c0); ++ /* tx obs enable and rx cm enable */ ++ reg |= (BIT(3) | BIT(4)); ++ writel(reg, u3phy_port->base + 0x0c0); ++ ++ udelay(1); ++ } else { ++ reg = readl(u3phy_port->base + 0x1a8); ++ reg |= BIT(4); /* ldo power down */ ++ writel(reg, u3phy_port->base + 0x1a8); ++ ++ reg = readl(u3phy_port->base + 0x044); ++ reg |= BIT(4); /* bg power down */ ++ writel(reg, u3phy_port->base + 0x044); ++ ++ reg = readl(u3phy_port->base + 0x150); ++ reg &= ~BIT(6); /* tx bias disable */ ++ writel(reg, u3phy_port->base + 0x150); ++ ++ reg = readl(u3phy_port->base + 0x080); ++ reg |= BIT(2); /* tx cm power down */ ++ writel(reg, u3phy_port->base + 0x080); ++ ++ reg = readl(u3phy_port->base + 0x0c0); ++ /* tx obs disable and rx cm disable */ ++ reg &= ~(BIT(3) | BIT(4)); ++ writel(reg, u3phy_port->base + 0x0c0); ++ } ++ ++ return 0; ++} ++ ++static int rk3328_u3phy_tuning(struct rockchip_u3phy *u3phy, ++ struct rockchip_u3phy_port *u3phy_port, ++ 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 */ ++ of_property_read_u32(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 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_pipe_power = rk3328_u3phy_pipe_power, ++ .phy_tuning = rk3328_u3phy_tuning, ++ }, ++ { /* sentinel */ } ++}; ++ ++static const struct of_device_id rockchip_u3phy_dt_match[] = { ++ { .compatible = "rockchip,rk3328-u3phy", .data = &rk3328_u3phy_cfgs }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, rockchip_u3phy_dt_match); ++ ++static struct platform_driver rockchip_u3phy_driver = { ++ .probe = rockchip_u3phy_probe, ++ .driver = { ++ .name = "rockchip-u3phy", ++ .of_match_table = rockchip_u3phy_dt_match, ++ }, ++}; ++module_platform_driver(rockchip_u3phy_driver); ++ ++MODULE_AUTHOR("Frank Wang "); ++MODULE_AUTHOR("William Wu "); ++MODULE_DESCRIPTION("Rockchip USB 3.0 PHY driver"); ++MODULE_LICENSE("GPL v2"); + +diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb3.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb3.yaml +new file mode 100644 +index 000000000000..f4f28625173a +--- /dev/null ++++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb3.yaml +@@ -0,0 +1,157 @@ ++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: "http://devicetree.org/schemas/phy/phy-rockchip-inno-usb3.yaml#" ++$schema: "http://devicetree.org/meta-schemas/core.yaml#" ++ ++title: ROCKCHIP USB 3.0 PHY WITH INNO IP BLOCK ++ ++maintainers: ++ ++properties: ++ compatible: ++ enum: ++ - rockchip,rk3328-u3phy ++ ++ reg: ++ - description: the base address of the USB 3.0 PHY ++ ++ interrupts: ++ maxItems: 1 ++ ++ interrupt-names: ++ items: ++ - const: linestate ++ description: host/otg linestate interrupt ++ ++ clocks: ++ maxItems: 2 ++ ++ clock-names: ++ items: ++ - const: u3phy-otg ++ description: USB 3.0 PHY UTMI ++ - const: u3phy-pipe ++ description: USB 3.0 PHY Pipe ++ ++ resets: ++ maxItems: 6 ++ ++ reset-names: ++ items: ++ - const: u3phy-u2-por ++ description: USB 2.0 logic of USB 3.0 PHY ++ - const: u3phy-u3-por ++ description: USB 3.0 logic of USB 3.0 PHY ++ - const: u3phy-pipe-mac ++ description: USB 3.0 PHY pipe MAC ++ - const: u3phy-utmi-mac ++ description: USB 3.0 PHY utmi MAC ++ - const: u3phy-utmi-apb ++ description: USB 3.0 PHY utmi apb ++ - const: u3phy-pipe-apb ++ description: USB 3.0 PHY pipe apb ++ ++ "#phy-cells": ++ const: 1 ++ ++ rockchip,u3phygrf: ++ $ref: /schemas/types.yaml#/definitions/phandle-array ++ type: array ++ - description: phandle to the syscon managing the ++ "USB 3.0 PHY general register files". ++ ++ vbus-drv-gpios: ++ $ref: /schemas/types.yaml#/definitions/phandle-array ++ type: array ++ - description: phandle for gpio vbus supply ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - interrupt-names ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ - "#phy-cells" ++ - rockchip,u3phygrf ++ ++patternProperties: ++ "^u3phy_utmi@[0-9a-f]+$": ++ type: object ++ ++ properties: ++ - description: USB 2.0 utmi phy. ++ ++ rockchip,odt-val-tuning: ++ type: boolean ++ - description: specify 45ohm ODT tuning value. ++ ++ "phy-cells": ++ const: 0 ++ ++ required: ++ - reg ++ - "#phy-cells" ++ ++patternProperties: ++ "^u3phy_pipe@[0-9a-f]+$": ++ type: object ++ ++ properties: ++ - description: USB 3.0 pipe phy. ++ ++ rockchip,refclk-25m-quirk : ++ ++ - description: phy reference clock changed to 25m quirk. ++ ++ "phy-cells": ++ const: 0 ++ ++ required: ++ - reg ++ - "#phy-cells" ++ ++examples: ++ ++usb3phy_grf: syscon@ff460000 { ++ compatible = "rockchip,usb3phy-grf", "syscon"; ++ reg = <0x0 0xff460000 0x0 0x1000>; ++}; ++ ++... ++ ++u3phy: usb3-phy@ff470000 { ++ compatible = "rockchip,rk3328-u3phy"; ++ reg = <0x0 0xff470000 0x0 0x0>; ++ rockchip,u3phygrf = <&usb3phy_grf>; ++ interrupts = ; ++ interrupt-names = "linestate"; ++ clocks = <&cru PCLK_USB3PHY_OTG>, <&cru PCLK_USB3PHY_PIPE>; ++ clock-names = "u3phy-otg", "u3phy-pipe"; ++ resets = <&cru SRST_USB3PHY_U2>, ++ <&cru SRST_USB3PHY_U3>, ++ <&cru SRST_USB3PHY_PIPE>, ++ <&cru SRST_USB3OTG_UTMI>, ++ <&cru SRST_USB3PHY_OTG_P>, ++ <&cru SRST_USB3PHY_PIPE_P>; ++ reset-names = "u3phy-u2-por", "u3phy-u3-por", ++ "u3phy-pipe-mac", "u3phy-utmi-mac", ++ "u3phy-utmi-apb", "u3phy-pipe-apb"; ++ vbus-drv-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ u3phy_utmi: utmi@ff470000 { ++ reg = <0x0 0xff470000 0x0 0x8000>; ++ #phy-cells = <0>; ++ }; ++ ++ u3phy_pipe: pipe@ff478000 { ++ reg = <0x0 0xff478000 0x0 0x8000>; ++ #phy-cells = <0>; ++ }; ++}; +diff --git a/Documentation/devicetree/bindings/soc/rockchip/grf.txt b/Documentation/devicetree/bindings/soc/rockchip/grf.txt +index ada5435ce2c3..5f2f19344cc7 100644 +--- a/Documentation/devicetree/bindings/soc/rockchip/grf.txt ++++ b/Documentation/devicetree/bindings/soc/rockchip/grf.txt +@@ -34,6 +34,8 @@ Required Properties: + - "rockchip,rk3328-usb2phy-grf", "syscon": for rk3328 + - compatible: USBGRF should be one of the following: + - "rockchip,rv1108-usbgrf", "syscon": for rv1108 ++- compatible: USB3PHYGRF should be one of the following: ++ - "rockchip,u3phy-grf", "syscon" + - reg: physical base address of the controller and length of memory mapped + region. + +diff --git a/arch/arm64/boot/dts/rockchip/rk3328.dtsi b/arch/arm64/boot/dts/rockchip/rk3328.dtsi +index 31cc1541f1f5..072e988ad655 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3328.dtsi ++++ b/arch/arm64/boot/dts/rockchip/rk3328.dtsi +@@ -805,6 +805,47 @@ + }; + }; + ++ usb3phy_grf: syscon@ff460000 { ++ compatible = "rockchip,usb3phy-grf", "syscon"; ++ reg = <0x0 0xff460000 0x0 0x1000>; ++ }; ++ ++ u3phy: usb3-phy@ff470000 { ++ compatible = "rockchip,rk3328-u3phy"; ++ reg = <0x0 0xff470000 0x0 0x0>; ++ rockchip,u3phygrf = <&usb3phy_grf>; ++ rockchip,grf = <&grf>; ++ interrupts = ; ++ interrupt-names = "linestate"; ++ clocks = <&cru PCLK_USB3PHY_OTG>, <&cru PCLK_USB3PHY_PIPE>; ++ clock-names = "u3phy-otg", "u3phy-pipe"; ++ resets = <&cru SRST_USB3PHY_U2>, ++ <&cru SRST_USB3PHY_U3>, ++ <&cru SRST_USB3PHY_PIPE>, ++ <&cru SRST_USB3OTG_UTMI>, ++ <&cru SRST_USB3PHY_OTG_P>, ++ <&cru SRST_USB3PHY_PIPE_P>; ++ reset-names = "u3phy-u2-por", "u3phy-u3-por", ++ "u3phy-pipe-mac", "u3phy-utmi-mac", ++ "u3phy-utmi-apb", "u3phy-pipe-apb"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ status = "disabled"; ++ ++ u3phy_utmi: utmi@ff470000 { ++ reg = <0x0 0xff470000 0x0 0x8000>; ++ #phy-cells = <0>; ++ status = "disabled"; ++ }; ++ ++ u3phy_pipe: pipe@ff478000 { ++ reg = <0x0 0xff478000 0x0 0x8000>; ++ #phy-cells = <0>; ++ status = "disabled"; ++ }; ++ }; ++ + sdmmc: dwmmc@ff500000 { + compatible = "rockchip,rk3328-dw-mshc", "rockchip,rk3288-dw-mshc"; + reg = <0x0 0xff500000 0x0 0x4000>; +@@ -936,6 +977,37 @@ + status = "disabled"; + }; + ++ usbdrd3: usb@ff600000 { ++ compatible = "rockchip,rk3328-dwc3", "rockchip,rk3399-dwc3"; ++ clocks = <&cru SCLK_USB3OTG_REF>, <&cru ACLK_USB3OTG>, ++ <&cru SCLK_USB3OTG_SUSPEND>; ++ clock-names = "ref", "bus_early", ++ "suspend"; ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ clock-ranges; ++ status = "disabled"; ++ ++ usbdrd_dwc3: dwc3@ff600000 { ++ compatible = "snps,dwc3"; ++ reg = <0x0 0xff600000 0x0 0x100000>; ++ interrupts = ; ++ dr_mode = "otg"; ++ phys = <&u3phy_utmi>, <&u3phy_pipe>; ++ phy-names = "usb2-phy", "usb3-phy"; ++ phy_type = "utmi_wide"; ++ snps,dis_enblslpm_quirk; ++ snps,dis-u2-freeclk-exists-quirk; ++ snps,dis_u2_susphy_quirk; ++ snps,dis_u3_susphy_quirk; ++ snps,dis-del-phy-power-chg-quirk; ++ snps,dis-tx-ipgap-linecheck-quirk; ++ snps,xhci-trb-ent-quirk; ++ status = "disabled"; ++ }; ++ }; ++ + gic: interrupt-controller@ff811000 { + compatible = "arm,gic-400"; + #interrupt-cells = <3>; + +diff --git a/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts b/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts +index bb40c163b05d..f300f3d0f02e 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts ++++ b/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts +@@ -328,6 +328,18 @@ + status = "okay"; + }; + ++&u3phy { ++ status = "okay"; ++}; ++ ++&u3phy_utmi { ++ status = "okay"; ++}; ++ ++&u3phy_pipe { ++ status = "okay"; ++}; ++ + &uart2 { + status = "okay"; + }; +@@ -344,6 +356,15 @@ + status = "okay"; + }; + ++&usbdrd3 { ++ status = "okay"; ++}; ++ ++&usbdrd_dwc3 { ++ dr_mode = "host"; ++ status = "okay"; ++}; ++ + &vop { + status = "okay"; + }; + +diff --git a/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts b/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts +index 62936b432..f97446924 100644 +--- a/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts ++++ b/arch/arm64/boot/dts/rockchip/rk3328-rock64.dts +@@ -383,6 +383,18 @@ + status = "okay"; + }; + ++&u3phy { ++ status = "okay"; ++}; ++ ++&u3phy_utmi { ++ status = "okay"; ++}; ++ ++&u3phy_pipe { ++ status = "okay"; ++}; ++ + &usb_host0_ehci { + status = "okay"; + }; +@@ -391,6 +403,15 @@ + status = "okay"; + }; + ++&usbdrd3 { ++ status = "okay"; ++}; ++ ++&usbdrd_dwc3 { ++ dr_mode = "host"; ++ status = "okay"; ++}; ++ + &vop { + status = "okay"; + };