From patchwork Tue Jan 23 13:00:51 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kunihiko Hayashi X-Patchwork-Id: 125508 Delivered-To: patch@linaro.org Received: by 10.46.66.141 with SMTP id h13csp1741252ljf; Tue, 23 Jan 2018 05:01:34 -0800 (PST) X-Google-Smtp-Source: AH8x224spy2/rqf0eXzJlC5iyxEcjrGjieqcupPJKZXhGTbCh64UyGX4Ca9PdlsrYIIlGyU8M2+E X-Received: by 2002:a17:902:2c01:: with SMTP id m1-v6mr5400684plb.15.1516712494708; Tue, 23 Jan 2018 05:01:34 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1516712494; cv=none; d=google.com; s=arc-20160816; b=kdYCEPTm3hmUDN5dcr4laGoxZnqX54XrexjhFuOoNBf73bTxLHGWUyAxcV0fSXYdhY XHsWI1qJyVZEsOXiNtpmd4ZGOV0KKYIMZfqfFC2pp7fUpZgcxwE73GEGd3Jg9tM/DEo6 PhJeUBcAi4lXA/+bvzD0AXTwboROJVOXmcXV0++22lxgiB98x7WbaBFUtnIoY5zgzRWR YnKHnhAUpdbeD988WyVq99AMtoIyjY9TnY8Z8l9DhqEMKo7578en87txQJMIHLksxSrJ +TZzQN0qQ0zJy/dqlxPqmWn8ozbshua9uYumkShiU/MbljzYHlkd9xBw1CZ3WIy1ksFZ HpMg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:arc-authentication-results; bh=LSSWd0S4Hn/k1otx/msgkce8OD5kVpIMd34N+VgR0jQ=; b=Nounoq8C2hOURuRfNP/5NkJtssNIm2AIpnGJkYA5kjDhFswLIFAZML5w1mT8lHmJQY Ykfh2YlTrHu7Be/9JJvtTXppIrvnN/1alLCcV0kRU/9iw2ZfKrK7g+rDPFzuZosCyjRK cLnWto/78xwf59ypu6MfoFJz/dDcXrxEzLVoK8+dC8OgSq0IdbxRphyi2TgOeahTSpZu ZVrk/Qleqad5Ui9LGLkaQ3cge+8xtaS77Q6wAoo5ULu632yqlVk38dtrxPaYkrqbaVDQ IJ6/ED9EnJNmuh1LmOS4lCYUJwsajZaQUynB35WAWr0I2yVWldNEs18SiblgZSvt56jq KqLA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id n16si15005766pgc.761.2018.01.23.05.01.31; Tue, 23 Jan 2018 05:01:34 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751900AbeAWNB3 (ORCPT + 28 others); Tue, 23 Jan 2018 08:01:29 -0500 Received: from mx.socionext.com ([202.248.49.38]:59270 "EHLO mx.socionext.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751495AbeAWNA5 (ORCPT ); Tue, 23 Jan 2018 08:00:57 -0500 Received: from unknown (HELO kinkan-ex.css.socionext.com) ([172.31.9.52]) by mx.socionext.com with ESMTP; 23 Jan 2018 22:00:55 +0900 Received: from mail.mfilter.local (m-filter-2 [10.213.24.62]) by kinkan-ex.css.socionext.com (Postfix) with ESMTP id D79FD180B38; Tue, 23 Jan 2018 22:00:55 +0900 (JST) Received: from 172.31.9.51 (172.31.9.51) by m-FILTER with ESMTP; Tue, 23 Jan 2018 22:01:06 +0900 Received: from plum.e01.socionext.com (unknown [10.213.132.32]) by kinkan.css.socionext.com (Postfix) with ESMTP id 371D71A1704; Tue, 23 Jan 2018 22:00:55 +0900 (JST) From: Kunihiko Hayashi To: Felipe Balbi , linux-usb@vger.kernel.org Cc: Greg Kroah-Hartman , Masahiro Yamada , Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Jassi Brar , Masami Hiramatsu , Kunihiko Hayashi Subject: [PATCH 1/4] dt-bindings: dwc3: add binding documentation for UniPhier dwc3 glue driver Date: Tue, 23 Jan 2018 22:00:51 +0900 Message-Id: <1516712454-2915-2-git-send-email-hayashi.kunihiko@socionext.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1516712454-2915-1-git-send-email-hayashi.kunihiko@socionext.com> References: <1516712454-2915-1-git-send-email-hayashi.kunihiko@socionext.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Add devicetree binding documentation for dwc3 glue driver implemented on Socionext UniPhier SoCs. Signed-off-by: Kunihiko Hayashi --- .../devicetree/bindings/usb/dwc3-uniphier.txt | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/dwc3-uniphier.txt -- 2.7.4 diff --git a/Documentation/devicetree/bindings/usb/dwc3-uniphier.txt b/Documentation/devicetree/bindings/usb/dwc3-uniphier.txt new file mode 100644 index 0000000..677e072 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/dwc3-uniphier.txt @@ -0,0 +1,58 @@ +UniPhier DWC3 glue layer + +This describes the devicetree bindings for dwc3-uniphier driver implemented on +Socionext UniPhier SoCs. + +Required properties: +- compatible: + - "socionext,uniphier-pxs2-dwc3" : For UniPhier PXs2 SoC + - "socionext,uniphier-ld20-dwc3" : For UniPhier LD20 SoC +- reg: Address and range of the glue logic +- clocks: List of phandles for the clocks, and the number of phandles depends + on SoC platform. + +Optional properties: +- resets: List of phandles for the resets, and the number of phandles depends + on SoC platform. +- nvmem-cells: Phandles to nvmem cell that contains the trimming data. + Available only for LD20, and if unspecified, default value is used. +- nvmem-cell-names: Should be the following names, which correspond to each + nvmem-cells. N is the number indicating a port of phy. + All of the 3 parameters associated with the following names are + required for each port, if any one is omitted, the trimming data + of the port will not be set at all. + - "rtermN", "sel_tN", "hs_iN" : Each cell name for phy parameters + +Required child node: +A child node must exist to represent the core DWC3 IP block. The name of +the node is not important. The content of the node is defined in dwc3.txt. + +Example: + + usb: usb@65b00000 { + compatible = "socionext,uniphier-ld20-dwc3"; + reg = <0x65b00000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + clocks = <&sys_clk 14>, <&sys_clk 16>, <&sys_clk 17>; + resets = <&sys_rst 12>, <&sys_rst 16>, <&sys_rst 17>, + <&sys_rst 18>, <&sys_rst 19>; + nvmem-cells = <&usb_rterm0>, <&usb_rterm1>, + <&usb_rterm2>, <&usb_rterm3>, + <&usb_sel_t0>, <&usb_sel_t1>, + <&usb_sel_t2>, <&usb_sel_t3>, + <&usb_hs_i0>, <&usb_hs_i0>, + <&usb_hs_i2>, <&usb_hs_i2>; + nvmem-cell-names = "rterm0", "rterm1", "rterm2", "rterm3", + "sel_t0", "sel_t1", "sel_t2", "sel_t3", + "hs_i0", "hs_i1", "hs_i2", "hs_i3"; + ranges; + + dwc3@65a00000 { + compatible = "snps,dwc3"; + reg = <0x65a00000 0xcd00>; + interrupt-names = "host"; + interrupts = <0 134 4>; + dr_mode = "host"; + }; + }; From patchwork Tue Jan 23 13:00:52 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kunihiko Hayashi X-Patchwork-Id: 125509 Delivered-To: patch@linaro.org Received: by 10.46.66.141 with SMTP id h13csp1741919ljf; Tue, 23 Jan 2018 05:02:19 -0800 (PST) X-Google-Smtp-Source: AH8x227R6wN30qkirnj2Bjs/91B23Y2bQkpknIa7uiV1RaBoppF9cIpoDy5yvW0YbrIuVdaf0Cfb X-Received: by 2002:a17:902:6005:: with SMTP id r5-v6mr5659817plj.307.1516712539688; Tue, 23 Jan 2018 05:02:19 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1516712539; cv=none; d=google.com; s=arc-20160816; b=p/UD3TmBdeMkzIycN5p3lBWiuSs29wxppsIit1iryNImzFfDYqf3jcQdu90y5/axp2 hj4eGKUQopUfJsnMh2Af80MdMTauhVWpy4K0Kno5ygueG4SkTl+g/N0P+IJFLpQPjvsV +jMlyfHKqQ7AZQKKKoRKgOFZ4bIhtoq0jALFJritFEOG78ENy7uTuN8ST8VkIzZscMH9 cWLewd/kUilbMNPkzCyDWQOc9lDgFeVFNZf78H2iUce9d1WnkVvG30LN51gzemnfYtQd jBEKxEBMgAM6U61OTUx6oIPUMw6sHf8lXyg23cni5mWDVfPzhvnxPg4qxG2/JYY/lMDb U+eQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:arc-authentication-results; bh=ssYHkiRUQE2ApfkRsg2EL1kWyPBtVqZAfBWKzmcQ+I0=; b=vQmYQTf/A573SsaDZEQOZK2HJzInm2rQ6gbxlmQA8NApZS/0ij8e1yRByVq7rYlyIB DVunB1c2skp86qM6TyehXExniNoKWAUE+QuqsjgWuUBLhtiVJvszHtrC2PbG0sCosvwF VCnXsSRvnvEC4eIwsp/Q5RrshHHUsD33oX9YjhRzCl88iCp2n9JBWV82i3uFkhNzgFhr jGnUg8Z1q3Tw2RcMOBJojX1LT9kAD564fYdAOa+P6M9++Lj2N3CHitv/zbLOro0NOu5n r5MTzfdCVhzcP6miE9/18AFpdmBzgomEHS7CslaQRJyBnH73gr9zMrT1NNzewfCv1AfT sKDA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id u12si14484064pgv.826.2018.01.23.05.02.16; Tue, 23 Jan 2018 05:02:19 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751940AbeAWNCO (ORCPT + 28 others); Tue, 23 Jan 2018 08:02:14 -0500 Received: from mx.socionext.com ([202.248.49.38]:59265 "EHLO mx.socionext.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751474AbeAWNA5 (ORCPT ); Tue, 23 Jan 2018 08:00:57 -0500 Received: from unknown (HELO iyokan-ex.css.socionext.com) ([172.31.9.54]) by mx.socionext.com with ESMTP; 23 Jan 2018 22:00:55 +0900 Received: from mail.mfilter.local (m-filter-1 [10.213.24.61]) by iyokan-ex.css.socionext.com (Postfix) with ESMTP id D53E3600D0; Tue, 23 Jan 2018 22:00:55 +0900 (JST) Received: from 172.31.9.51 (172.31.9.51) by m-FILTER with ESMTP; Tue, 23 Jan 2018 22:01:03 +0900 Received: from plum.e01.socionext.com (unknown [10.213.132.32]) by kinkan.css.socionext.com (Postfix) with ESMTP id 77BAD1A0DED; Tue, 23 Jan 2018 22:00:55 +0900 (JST) From: Kunihiko Hayashi To: Felipe Balbi , linux-usb@vger.kernel.org Cc: Greg Kroah-Hartman , Masahiro Yamada , Rob Herring , Mark Rutland , devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Jassi Brar , Masami Hiramatsu , Kunihiko Hayashi Subject: [PATCH 2/4] usb: dwc3: add dwc3 glue layer for UniPhier SoCs Date: Tue, 23 Jan 2018 22:00:52 +0900 Message-Id: <1516712454-2915-3-git-send-email-hayashi.kunihiko@socionext.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1516712454-2915-1-git-send-email-hayashi.kunihiko@socionext.com> References: <1516712454-2915-1-git-send-email-hayashi.kunihiko@socionext.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Add a specific glue layer for UniPhier SoC platform to support USB host mode. It manages hardware operating sequences to enable multiple clock gates and assert resets, and to prepare to use dwc3 controller on the SoC. This patch also handles the physical layer that has same register space as the glue layer, because it needs to integrate initialziation sequence between glue and phy. In case of some SoCs, since some initialization values for PHY are included in nvmem, this patch includes the way to get the values from nvmem. It supports PXs2 and LD20 SoCs. Signed-off-by: Kunihiko Hayashi Signed-off-by: Motoya Tanigawa Signed-off-by: Masami Hiramatsu --- drivers/usb/dwc3/Kconfig | 9 + drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-uniphier.c | 554 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 564 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-uniphier.c -- 2.7.4 diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index ab8c0e0..a5cadc6 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -106,4 +106,13 @@ config USB_DWC3_ST inside (i.e. STiH407). Say 'Y' or 'M' if you have one such device. +config USB_DWC3_UNIPHIER + tristate "Socionext UniPhier Platforms" + depends on (ARCH_UNIPHIER || COMPILE_TEST) && OF + default USB_DWC3 + help + Support USB2/3 functionality in UniPhier platforms. + Say 'Y' or 'M' if your system that UniPhier SoC is implemented + has USB controllers based on DWC USB3 IP. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 7ac7250..31e82b3 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -48,3 +48,4 @@ obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o +obj-$(CONFIG_USB_DWC3_UNIPHIER) += dwc3-uniphier.o diff --git a/drivers/usb/dwc3/dwc3-uniphier.c b/drivers/usb/dwc3/dwc3-uniphier.c new file mode 100644 index 0000000..58e84cd --- /dev/null +++ b/drivers/usb/dwc3/dwc3-uniphier.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * dwc3-uniphier.c - Socionext UniPhier DWC3 specific glue layer + * + * Copyright 2015-2018 Socionext Inc. + * + * Author: + * Kunihiko Hayashi + * Contributors: + * Motoya Tanigawa + * Masami Hiramatsu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RESET_CTL 0x000 +#define LINK_RESET BIT(15) + +#define VBUS_CONTROL(n) (0x100 + 0x10 * (n)) +#define DRVVBUS_REG BIT(4) +#define DRVVBUS_REG_EN BIT(3) + +#define U2PHY_CFG0(n) (0x200 + 0x10 * (n)) +#define U2PHY_CFG0_HS_I_MASK GENMASK(31, 28) +#define U2PHY_CFG0_HSDISC_MASK GENMASK(27, 26) +#define U2PHY_CFG0_SWING_MASK GENMASK(17, 16) +#define U2PHY_CFG0_SEL_T_MASK GENMASK(15, 12) +#define U2PHY_CFG0_RTERM_MASK GENMASK(7, 6) +#define U2PHY_CFG0_TRIMMASK (U2PHY_CFG0_HS_I_MASK \ + | U2PHY_CFG0_SEL_T_MASK \ + | U2PHY_CFG0_RTERM_MASK) + +#define U2PHY_CFG1(n) (0x204 + 0x10 * (n)) +#define U2PHY_CFG1_DAT_EN BIT(29) +#define U2PHY_CFG1_ADR_EN BIT(28) +#define U2PHY_CFG1_ADR_MASK GENMASK(27, 16) +#define U2PHY_CFG1_DAT_MASK GENMASK(23, 16) + +#define U3PHY_TESTI(n) (0x300 + 0x10 * (n)) +#define U3PHY_TESTO(n) (0x304 + 0x10 * (n)) +#define TESTI_DAT_MASK GENMASK(13, 6) +#define TESTI_ADR_MASK GENMASK(5, 1) +#define TESTI_WR_EN BIT(0) + +#define HOST_CONFIG0 0x400 +#define NUM_U3_MASK GENMASK(13, 11) +#define NUM_U2_MASK GENMASK(10, 8) + +#define PHY_MAX_PARAMS 32 + +struct dwc3u_phy_param { + u32 addr; + u32 mask; + u32 val; +}; + +struct dwc3u_trim_param { + u32 rterm; + u32 sel_t; + u32 hs_i; +}; + +#define trim_param_is_valid(p) ((p)->rterm || (p)->sel_t || (p)->hs_i) + +struct dwc3u_priv { + struct device *dev; + void __iomem *base; + struct clk **clks; + int nclks; + struct reset_control *rst; + int nvbus; + const struct dwc3u_soc_data *data; +}; + +struct dwc3u_soc_data { + int ss_nparams; + struct dwc3u_phy_param ss_param[PHY_MAX_PARAMS]; + int hs_nparams; + struct dwc3u_phy_param hs_param[PHY_MAX_PARAMS]; + u32 hs_config0; + u32 hs_config1; + void (*trim_func)(struct dwc3u_priv *priv, u32 *pconfig, + struct dwc3u_trim_param *trim); +}; + +static inline u32 dwc3u_read(struct dwc3u_priv *priv, off_t offset) +{ + return readl(priv->base + offset); +} + +static inline void dwc3u_write(struct dwc3u_priv *priv, + off_t offset, u32 val) +{ + writel(val, priv->base + offset); +} + +static inline void dwc3u_maskwrite(struct dwc3u_priv *priv, + off_t offset, u32 mask, u32 val) +{ + u32 tmp; + + tmp = dwc3u_read(priv, offset); + dwc3u_write(priv, offset, (tmp & ~mask) | (val & mask)); +} + +static int dwc3u_get_hsport_num(struct dwc3u_priv *priv) +{ + return FIELD_GET(NUM_U2_MASK, dwc3u_read(priv, HOST_CONFIG0)); +} + +static int dwc3u_get_ssport_num(struct dwc3u_priv *priv) +{ + return FIELD_GET(NUM_U3_MASK, dwc3u_read(priv, HOST_CONFIG0)); +} + +static int dwc3u_get_nvparam(struct dwc3u_priv *priv, + const char *basename, int index, u8 *dst, + int maxlen) +{ + struct nvmem_cell *cell; + char name[16]; + size_t len; + u8 *buf; + + snprintf(name, sizeof(name) - 1, "%s%d", basename, index); + memset(dst, 0, maxlen); + + cell = nvmem_cell_get(priv->dev, name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + len = min_t(u32, len, maxlen); + memcpy(dst, buf, len); + kfree(buf); + + return 0; +} + +static int dwc3u_get_nvparam_u32(struct dwc3u_priv *priv, + const char *basename, int index, u32 *p_val) +{ + return dwc3u_get_nvparam(priv, basename, index, (u8 *)p_val, + sizeof(u32)); +} + +static void dwc3u_ssphy_testio_write(struct dwc3u_priv *priv, int port, + u32 data) +{ + /* need to read TESTO twice after accessing TESTI */ + dwc3u_write(priv, U3PHY_TESTI(port), data); + dwc3u_read(priv, U3PHY_TESTO(port)); + dwc3u_read(priv, U3PHY_TESTO(port)); +} + +static void dwc3u_ssphy_set_param(struct dwc3u_priv *priv, int port, + const struct dwc3u_phy_param *p) +{ + u32 val, val_prev; + + /* read previous data */ + dwc3u_ssphy_testio_write(priv, port, + FIELD_PREP(TESTI_DAT_MASK, 1) | + FIELD_PREP(TESTI_ADR_MASK, p->addr)); + val_prev = dwc3u_read(priv, U3PHY_TESTO(port)); + + /* update value */ + val = FIELD_PREP(TESTI_DAT_MASK, + (val_prev & ~p->mask) | (p->val & p->mask)) | + FIELD_PREP(TESTI_ADR_MASK, p->addr); + + dwc3u_ssphy_testio_write(priv, port, val); + dwc3u_ssphy_testio_write(priv, port, val | TESTI_WR_EN); + dwc3u_ssphy_testio_write(priv, port, val); + + /* read current data as dummy */ + dwc3u_ssphy_testio_write(priv, port, + FIELD_PREP(TESTI_DAT_MASK, 1) | + FIELD_PREP(TESTI_ADR_MASK, p->addr)); + dwc3u_read(priv, U3PHY_TESTO(port)); +} + +static void dwc3u_ssphy_init(struct dwc3u_priv *priv) +{ + int nparams = min_t(u32, priv->data->ss_nparams, PHY_MAX_PARAMS); + int nports = dwc3u_get_ssport_num(priv); + int i, j; + + for (i = 0; i < nports; i++) + for (j = 0; j < nparams; j++) + dwc3u_ssphy_set_param(priv, i, + &priv->data->ss_param[j]); +} + +static void dwc3u_hsphy_trim_ld20(struct dwc3u_priv *priv, u32 *pconfig, + struct dwc3u_trim_param *ptrim) +{ + *pconfig = (*pconfig & ~U2PHY_CFG0_TRIMMASK) | + FIELD_PREP(U2PHY_CFG0_RTERM_MASK, ptrim->rterm) | + FIELD_PREP(U2PHY_CFG0_SEL_T_MASK, ptrim->sel_t) | + FIELD_PREP(U2PHY_CFG0_HS_I_MASK, ptrim->hs_i); +} + +static int dwc3u_hsphy_get_nvparams(struct dwc3u_priv *priv, int port, + struct dwc3u_trim_param *ptrim) +{ + int ret; + + ret = dwc3u_get_nvparam_u32(priv, "rterm", port, &ptrim->rterm); + if (ret) + return ret; + + ret = dwc3u_get_nvparam_u32(priv, "sel_t", port, &ptrim->sel_t); + if (ret) + return ret; + + return dwc3u_get_nvparam_u32(priv, "hs_i", port, &ptrim->hs_i); +} + +static int dwc3u_hsphy_update_config(struct dwc3u_priv *priv, int port, + u32 *pconfig) +{ + struct dwc3u_trim_param trim; + int ret, trimmed = 0; + + if (priv->data->trim_func) { + ret = dwc3u_hsphy_get_nvparams(priv, port, &trim); + if (ret == -EPROBE_DEFER) + return ret; + + /* + * call trim_func only when trimming parameters that aren't + * all-zero can be acquired. All-zero parameters mean nothing + * has been written to nvmem. + */ + if (!ret && trim_param_is_valid(&trim)) { + priv->data->trim_func(priv, pconfig, &trim); + trimmed = 1; + } else { + dev_dbg(priv->dev, + "can't get parameter for port%d from nvmem\n", + port); + } + } + + /* use default parameters without trimming values */ + if (!trimmed) + *pconfig = (*pconfig & ~U2PHY_CFG0_HSDISC_MASK) | + FIELD_PREP(U2PHY_CFG0_HSDISC_MASK, 3); + + return 0; +} + +static void dwc3u_hsphy_set_config(struct dwc3u_priv *priv, int port, + u32 config0, u32 config1) +{ + dwc3u_write(priv, U2PHY_CFG0(port), config0); + dwc3u_write(priv, U2PHY_CFG1(port), config1); + + dwc3u_maskwrite(priv, U2PHY_CFG0(port), + U2PHY_CFG0_SWING_MASK, + FIELD_PREP(U2PHY_CFG0_SWING_MASK, 2)); +} + +static void dwc3u_hsphy_set_param(struct dwc3u_priv *priv, int port, + const struct dwc3u_phy_param *p) +{ + dwc3u_maskwrite(priv, U2PHY_CFG1(port), + U2PHY_CFG1_ADR_EN | U2PHY_CFG1_ADR_MASK, + U2PHY_CFG1_ADR_EN | + FIELD_PREP(U2PHY_CFG1_ADR_MASK, p->addr)); + dwc3u_maskwrite(priv, U2PHY_CFG1(port), + U2PHY_CFG1_ADR_EN, 0); + + dwc3u_maskwrite(priv, U2PHY_CFG1(port), + U2PHY_CFG1_DAT_EN | + FIELD_PREP(U2PHY_CFG1_DAT_MASK, p->mask), + U2PHY_CFG1_DAT_EN | + FIELD_PREP(U2PHY_CFG1_DAT_MASK, p->val)); + dwc3u_maskwrite(priv, U2PHY_CFG1(port), + U2PHY_CFG1_DAT_EN, 0); +} + +static int dwc3u_hsphy_init(struct dwc3u_priv *priv) +{ + int nparams = min(priv->data->hs_nparams, PHY_MAX_PARAMS); + int nports = dwc3u_get_hsport_num(priv); + u32 config0, config1; + int i, ret, port; + + for (port = 0; port < nports; port++) { + config0 = priv->data->hs_config0; + config1 = priv->data->hs_config1; + + ret = dwc3u_hsphy_update_config(priv, port, &config0); + if (ret) + return ret; + + dwc3u_hsphy_set_config(priv, port, config0, config1); + + for (i = 0; i < nparams; i++) + dwc3u_hsphy_set_param(priv, port, + &priv->data->hs_param[i]); + } + + return 0; +} + +static int dwc3u_phy_init(struct dwc3u_priv *priv) +{ + dwc3u_ssphy_init(priv); + + return dwc3u_hsphy_init(priv); +} + +static void dwc3u_vbus_enable(struct dwc3u_priv *priv) +{ + int i; + + for (i = 0; i < priv->nvbus; i++) { + dwc3u_maskwrite(priv, VBUS_CONTROL(i), + DRVVBUS_REG_EN | DRVVBUS_REG, + DRVVBUS_REG_EN | DRVVBUS_REG); + } +} + +static void dwc3u_vbus_disable(struct dwc3u_priv *priv) +{ + int i; + + for (i = 0; i < priv->nvbus; i++) { + dwc3u_maskwrite(priv, VBUS_CONTROL(i), + DRVVBUS_REG_EN | DRVVBUS_REG, + DRVVBUS_REG_EN | 0); + } +} + +static void dwc3u_reset_init(struct dwc3u_priv *priv) +{ + dwc3u_maskwrite(priv, RESET_CTL, LINK_RESET, 0); + usleep_range(1000, 2000); + dwc3u_maskwrite(priv, RESET_CTL, LINK_RESET, LINK_RESET); +} + +static void dwc3u_reset_clear(struct dwc3u_priv *priv) +{ + dwc3u_maskwrite(priv, RESET_CTL, LINK_RESET, 0); +} + +static int dwc3u_init(struct dwc3u_priv *priv) +{ + int nr_hsports, nr_ssports; + int ret; + + nr_hsports = dwc3u_get_hsport_num(priv); + nr_ssports = dwc3u_get_ssport_num(priv); + priv->nvbus = max(nr_hsports, nr_ssports); + + dwc3u_vbus_enable(priv); + + ret = dwc3u_phy_init(priv); + if (ret) + return ret; + + dwc3u_reset_init(priv); + + return 0; +} + +static void dwc3u_exit(struct dwc3u_priv *priv) +{ + dwc3u_reset_clear(priv); + dwc3u_vbus_disable(priv); +} + +static void dwc3u_disable_clk(struct dwc3u_priv *priv) +{ + int i; + + for (i = 0; i < priv->nclks; i++) { + clk_disable_unprepare(priv->clks[i]); + clk_put(priv->clks[i]); + } +} + +static int dwc3u_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node; + struct dwc3u_priv *priv; + struct resource *res; + struct clk *clk; + int i, nr_clks; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = of_device_get_match_data(dev); + if (WARN_ON(!priv->data)) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->dev = dev; + + node = dev->of_node; + nr_clks = of_clk_get_parent_count(node); + if (!nr_clks) { + dev_err(dev, "failed to get clock property\n"); + return -ENODEV; + } + + priv->clks = devm_kcalloc(priv->dev, nr_clks, sizeof(struct clk *), + GFP_KERNEL); + if (!priv->clks) + return -ENOMEM; + + for (i = 0; i < nr_clks; i++) { + clk = of_clk_get(node, i); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto out_clk_disable; + } + ret = clk_prepare_enable(clk); + if (ret < 0) { + clk_put(clk); + goto out_clk_disable; + } + priv->clks[i] = clk; + priv->nclks = i; + } + + priv->rst = devm_reset_control_array_get_optional_shared(priv->dev); + if (IS_ERR(priv->rst)) { + ret = PTR_ERR(priv->rst); + goto out_clk_disable; + } + ret = reset_control_deassert(priv->rst); + if (ret) + goto out_clk_disable; + + ret = dwc3u_init(priv); + if (ret) + goto out_rst_assert; + + platform_set_drvdata(pdev, priv); + + ret = of_platform_populate(node, NULL, NULL, priv->dev); + if (ret) + goto out_exit; + + return 0; + +out_exit: + dwc3u_exit(priv); +out_rst_assert: + reset_control_assert(priv->rst); +out_clk_disable: + dwc3u_disable_clk(priv); + + return ret; +} + +static int dwc3u_remove(struct platform_device *pdev) +{ + struct dwc3u_priv *priv = platform_get_drvdata(pdev); + + of_platform_depopulate(&pdev->dev); + dwc3u_exit(priv); + + reset_control_assert(priv->rst); + dwc3u_disable_clk(priv); + + return 0; +} + +static const struct dwc3u_soc_data dwc3u_pxs2_data = { + .ss_nparams = 7, + .ss_param = { + { 7, 0x0f, 0x0a }, + { 8, 0x0f, 0x03 }, + { 9, 0x0f, 0x05 }, + { 11, 0x0f, 0x09 }, + { 13, 0x60, 0x40 }, + { 27, 0x07, 0x07 }, + { 28, 0x03, 0x01 }, + }, + .hs_nparams = 0, +}; + +static const struct dwc3u_soc_data dwc3u_ld20_data = { + .ss_nparams = 3, + .ss_param = { + { 7, 0x0f, 0x06 }, + { 13, 0xff, 0xcc }, + { 26, 0xf0, 0x50 }, + }, + .hs_nparams = 1, + .hs_param = { + { 10, 0x60, 0x60 }, + }, + .trim_func = dwc3u_hsphy_trim_ld20, + .hs_config0 = 0x92306680, + .hs_config1 = 0x00000106, +}; + +static const struct of_device_id of_dwc3u_match[] = { + { + .compatible = "socionext,uniphier-pxs2-dwc3", + .data = &dwc3u_pxs2_data, + }, + { + .compatible = "socionext,uniphier-ld20-dwc3", + .data = &dwc3u_ld20_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_dwc3u_match); + +static struct platform_driver dwc3u_driver = { + .probe = dwc3u_probe, + .remove = dwc3u_remove, + .driver = { + .name = "uniphier-dwc3", + .of_match_table = of_dwc3u_match, + }, +}; + +module_platform_driver(dwc3u_driver); + +MODULE_AUTHOR("Kunihiko Hayashi "); +MODULE_DESCRIPTION("DesignWare USB3 UniPhier glue layer"); +MODULE_LICENSE("GPL v2");