From patchwork Sun Jul 12 03:01:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pablo Cholaky X-Patchwork-Id: 11657995 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5E80813B6 for ; Sun, 12 Jul 2020 03:02:16 +0000 (UTC) Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 1256F206F4 for ; Sun, 12 Jul 2020 03:02:15 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="PbZSENmr"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=slash.cl header.i=@slash.cl header.b="eIQEnqFc" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 1256F206F4 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=slash.cl Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-rockchip-bounces+patchwork-linux-rockchip=patchwork.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=merlin.20170209; h=Sender:Cc:Reply-To:List-Subscribe :List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Type: MIME-Version:Message-ID:Subject:From:To:Date:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:List-Owner; bh=HOkgXicfZWanHRCdEhTp+oDy1tbBRgpoGqrripMkv4w=; b=PbZSENmr6xNS5PMa2KspqGfQC0 pYK9PXpzPGIi0DpOH2jn4oDzRLOpbJnxOC4aJPgXjG3X+xR2yK4dRjJk1BdaPRcAh5bF2YKdA0jeb FfVrgpaFAYP8oRdumn/FIzgA/2s/VgaY71a6FzvmOehP1AZhe5leXsUgA38JfYztQ6R2ObpuLdy/p MZ9GVGw2amWtUzHwnnzznZ8s76zpPZqexAd59deC5SFDthq64/OKvFfa5zk+m8IN9IzA8XLhYN+f4 cypzTPiQSnufR+zu82+aQCBJPDgsB6NiHeZy7rdnmerwUMXYZLngVGaMRiIKRakamV0OXff9le1pJ fFQea7rg==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1juSFl-00070r-SV; Sun, 12 Jul 2020 03:02:09 +0000 Received: from mail-40136.protonmail.ch ([185.70.40.136]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1juSFf-0006zr-WC for linux-rockchip@lists.infradead.org; Sun, 12 Jul 2020 03:02:06 +0000 Date: Sun, 12 Jul 2020 03:01:54 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=slash.cl; s=protonmail; t=1594522920; bh=0/BraJXPVPelEo1iwEKNWSjybP3t8puaBkVSWfy1ttM=; h=Date:To:From:Cc:Reply-To:Subject:From; b=eIQEnqFcDDgabHR/rnQozDi2d/+esYtHhjjMaGQDPIO/CEvJyFWGJb+df45DjqMIW h2ioH4LQZfbE/Ru69hUc1cnBWQPmjDkllGbobcnTnJO2x6fQ7kBQkvi34ubv0c4FWi bm+CHdlHFt0/nyAcY5FNE434FMOoL2yoprW2N2WE= To: "heiko@sntech.de" , "linux-rockchip@lists.infradead.org" From: Pablo Cholaky Subject: [PATCH] Enable USB3 Rockchip PHY for RK3328-roc-cc Message-ID: MIME-Version: 1.0 X-Spam-Status: No, score=-1.2 required=7.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF shortcircuit=no autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on mail.protonmail.ch X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200711_230204_419095_5BB197AA X-CRM114-Status: GOOD ( 15.53 ) X-Spam-Score: -0.9 (/) X-Spam-Report: SpamAssassin version 3.4.4 on merlin.infradead.org summary: Content analysis details: (-0.9 points) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 RCVD_IN_MSPIKE_H4 RBL: Very Good reputation (+4) [185.70.40.136 listed in wl.mailspike.net] -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at https://www.dnswl.org/, low trust [185.70.40.136 listed in list.dnswl.org] -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.0 RCVD_IN_MSPIKE_WL Mailspike good senders X-BeenThere: linux-rockchip@lists.infradead.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Upstream kernel work for Rockchip platforms List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Pablo Cholaky Cc: "devicetree@vger.kernel.org" , "linux-arm-kernel@lists.infradead.org" Sender: "Linux-rockchip" Errors-To: linux-rockchip-bounces+patchwork-linux-rockchip=patchwork.kernel.org@lists.infradead.org Hi everyone, Please review/accept this patch for linux-rockchip for-next branch. Using RK3328-roc-cc vanilla kernel only makes 1/3 USB 2.0 working devices. This patch enables USB3 support and enable all USB devices. It been tested at Linux 5.6.0 and 5.7.8 with no problem, also changes shouldn't impact other settings due device tree nature. The only extra inclusion is PHY INNO USB3 which been took from https://github.com/free-z4u/roc-rk3328-cc Some extra minor changes been took from Peter Geis repository (https://github.com/pgwipeout/linux-rockchip) and also include some minor changes like: - Enable sound dais - Enable GPU - Enable PWM fan - Fix sdmmc detection on boot Many thanks. --- Pablo Cholaky commit d23ce9b53e77ffc4c3a3675af3b06fc69c989710 Author: Pablo Cholaky Date: Sat Jul 11 22:01:08 2020 -0400 USB3 support for Rockchip PHY. Used for RK3328-roc-cc Adds CONFIG_PHY_ROCKCHIP_INNO_USB3 and make it work for rk3328-roc-cc device tree setting. Add fixes for GPU/Sound/SDMMC for same device Signed-off-by: Pablo Cholaky diff --git a/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts b/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts index b70ffb1c6a63..1e5be2c036b1 100644 --- a/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts +++ b/arch/arm64/boot/dts/rockchip/rk3328-roc-cc.dts @@ -14,6 +14,32 @@ chosen { stdout-path = "serial2:1500000n8"; }; + sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,mclk-fs = <256>; + simple-audio-card,name = "rockchip,rk3328"; + simple-audio-card,cpu { + sound-dai = <&i2s1>; + }; + simple-audio-card,codec { + sound-dai = <&codec>; + }; + }; + + hdmi-sound { + compatible = "simple-audio-card"; + simple-audio-card,format = "i2s"; + simple-audio-card,mclk-fs = <128>; + simple-audio-card,name = "rockchip,hdmi"; + simple-audio-card,cpu { + sound-dai = <&i2s0>; + }; + simple-audio-card,codec { + sound-dai = <&hdmi>; + }; + }; + gmac_clkin: external-gmac-clock { compatible = "fixed-clock"; clock-frequency = <125000000>; @@ -83,6 +109,62 @@ vcc_phy: vcc-phy-regulator { regulator-boot-on; }; + fan0: pwm-fan { + compatible = "pwm-fan"; + pwms = <&pwm2 0 10000 0>; + cooling-min-state = <0>; + cooling-max-state = <12>; + #cooling-cells = <2>; + cooling-levels = <0 40 60 80 100 + 120 140 160 180 200 + 220 240 255>; + }; + + thermal-zones { + soc-thermal { + trips { + threshold: trip-point0 { + temperature = <40000>; + hysteresis = <10000>; + type = "active"; + }; + target: trip-point1 { + temperature = <50000>; + hysteresis = <10000>; + type = "active"; + }; + soc_crit: soc-crit { + temperature = <95000>; + hysteresis = <10000>; + type = "critical"; + }; + }; + + /delete-node/ cooling-maps; + cooling-maps { + map0 { + trip = <&threshold>; + cooling-device = <&fan0 1 3>; + contribution = <4096>; + }; + map1 { + trip = <&target>; + cooling-device = <&fan0 3 12>; + contribution = <4096>; + }; + map2 { + trip = <&soc_crit>; + cooling-device = <&cpu0 8 8>, + <&cpu1 8 8>, + <&cpu2 8 8>, + <&cpu3 8 8>, + <&fan0 8 8>; + contribution = <4096>; + }; + }; + }; + }; + leds { compatible = "gpio-leds"; @@ -104,6 +186,11 @@ user_led: led-1 { }; }; +&codec { + #sound-dai-cells = <0>; + status = "okay"; +}; + &cpu0 { cpu-supply = <&vdd_arm>; }; @@ -120,6 +207,20 @@ &cpu3 { cpu-supply = <&vdd_arm>; }; +&cpu0_opp_table { + opp-1392000000 { + opp-hz = /bits/ 64 <1392000000>; + opp-microvolt = <1325000>; + clock-latency-ns = <40000>; + }; + opp-1512000000 { + status = "disabled"; + opp-hz = /bits/ 64 <1512000000>; + opp-microvolt = <1350000>; + clock-latency-ns = <40000>; + }; +}; + &emmc { bus-width = <8>; cap-mmc-highspeed; @@ -270,6 +371,22 @@ regulator-state-mem { }; }; +&i2s0 { + #sound-dai-cells = <0>; + rockchip,bclk-fs = <128>; + status = "okay"; +}; + +&i2s1 { + #sound-dai-cells = <0>; + status = "okay"; +}; + +&gpu { + status = "okay"; + mali-supply = <&vdd_logic>; +}; + &io_domains { status = "okay"; @@ -294,6 +411,12 @@ usb20_host_drv: usb20-host-drv { rockchip,pins = <1 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>; }; }; + + usb3 { + usb30_host_drv: usb30-host-drv { + rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>; + }; + }; }; &sdmmc { @@ -319,29 +442,40 @@ &tsadc { &u2phy { status = "okay"; + + u2phy_host { + status = "okay"; + }; + + u2phy_otg { + status = "okay"; + dr_mode = "host"; + }; }; -&u2phy_host { +&usb20_otg { status = "okay"; + dr_mode = "host"; }; -&u2phy_otg { +&usb_host0_ehci { status = "okay"; }; -&uart2 { +&usb_host0_ohci { status = "okay"; }; -&usb20_otg { +&usbdrd3 { status = "okay"; }; -&usb_host0_ehci { +&usbdrd_dwc3 { + dr_mode = "host"; status = "okay"; }; -&usb_host0_ohci { +&uart2 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk3328.dtsi b/arch/arm64/boot/dts/rockchip/rk3328.dtsi index 72e655020560..ee7abde1c31d 100644 --- a/arch/arm64/boot/dts/rockchip/rk3328.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3328.dtsi @@ -982,6 +982,34 @@ usb_host0_ohci: usb@ff5d0000 { status = "disabled"; }; + usbdrd3: usb@ff600000 { + compatible = "rockchip,rk3328-dwc3", "rockchip,rk3399-dwc3"; + clocks = <&cru SCLK_USB3OTG_REF>, <&cru SCLK_USB3OTG_SUSPEND>, + <&cru ACLK_USB3OTG>; + clock-names = "ref_clk", "suspend_clk", + "bus_clk"; + #address-cells = <2>; + #size-cells = <2>; + ranges; + status = "disabled"; + + usbdrd_dwc3: dwc3@ff600000 { + compatible = "snps,dwc3"; + reg = <0x0 0xff600000 0x0 0x100000>; + interrupts = ; + dr_mode = "otg"; + 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/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 0824b9dd5683..f8c4c0d719ec 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -35,6 +35,14 @@ 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 && OF + select GENERIC_PHY + select USB_PHY + help + Support for Rockchip USB 3.0 PHY with Innosilicon IP block. + config PHY_ROCKCHIP_INNO_DSIDPHY tristate "Rockchip Innosilicon MIPI/LVDS/TTL PHY driver" depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile index 9f59a81e4e0d..02cd208e8e00 100644 --- a/drivers/phy/rockchip/Makefile +++ b/drivers/phy/rockchip/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY) += phy-rockchip-inno-dsidphy.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..8164250baca9 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb3.c @@ -0,0 +1,1150 @@ +/* + * Rockchip USB 3.0 PHY with Innosilicon IP block driver + * + * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_pipe_power)(struct rockchip_u3phy *, + struct rockchip_u3phy_port *, + bool on); + int (*phy_tuning)(struct rockchip_u3phy *, + struct rockchip_u3phy_port *, + struct device_node *); + int (*phy_calibrate)(struct rockchip_u3phy *, + struct rockchip_u3phy_port *); +}; + +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[U3PHY_MAX_CLKS]; + 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_MAX_CLKS && 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_MAX_CLKS - 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 int rockchip_u3phy_calibrate(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; + + if (u3phy->cfgs->phy_calibrate) { + /* + * When do USB3 compliance test, we may connect the oscilloscope + * front panel Aux Out to the DUT SSRX+, the Aux Out of the + * oscilloscope outputs a negative pulse whose width is between + * 300- 400 ns which may trigger some DUTs to change the CP test + * pattern. + * + * The Inno USB3 PHY disable the function to detect the negative + * pulse in SSRX+ by default, so we need to enable the function + * to toggle the CP test pattern before do USB3 compliance test. + */ + dev_dbg(u3phy->dev, "prepare for u3phy compliance test\n"); + ret = u3phy->cfgs->phy_calibrate(u3phy, u3phy_port); + if (ret) + return ret; + } + + 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, + .calibrate = rockchip_u3phy_calibrate, + .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); + } + + for (clk = 0; clk < U3PHY_MAX_CLKS; 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; + 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 int rk322xh_u3phy_calibrate_enable(struct rockchip_u3phy *u3phy, + struct rockchip_u3phy_port *u3phy_port) +{ + if (u3phy_port->type == U3PHY_TYPE_PIPE) { + writel(0x0c, u3phy_port->base + 0x408); + } else { + dev_err(u3phy->dev, "The u3phy type is not pipe\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, + .phy_calibrate = rk322xh_u3phy_calibrate_enable, + }, + { /* 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");