diff mbox series

[2/4] usb: dwc3: add dwc3 glue layer for UniPhier SoCs

Message ID 1516712454-2915-3-git-send-email-hayashi.kunihiko@socionext.com
State New
Headers show
Series [1/4] dt-bindings: dwc3: add binding documentation for UniPhier dwc3 glue driver | expand

Commit Message

Kunihiko Hayashi Jan. 23, 2018, 1 p.m. UTC
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 <hayashi.kunihiko@socionext.com>

Signed-off-by: Motoya Tanigawa <tanigawa.motoya@socionext.com>

Signed-off-by: Masami Hiramatsu <masami.hiramatsu@linaro.org>

---
 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

Comments

Felipe Balbi Jan. 23, 2018, 1:12 p.m. UTC | #1
Hi,

Kunihiko Hayashi <hayashi.kunihiko@socionext.com> writes:
> 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 <hayashi.kunihiko@socionext.com>

> Signed-off-by: Motoya Tanigawa <tanigawa.motoya@socionext.com>

> Signed-off-by: Masami Hiramatsu <masami.hiramatsu@linaro.org>

> ---

>  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

>

> 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 <hayashi.kunihiko@socionext.com>

> + * Contributors:

> + *      Motoya Tanigawa <tanigawa.motoya@socionext.com>

> + *      Masami Hiramatsu <masami.hiramatsu@linaro.org>

> + */

> +

> +#include <linux/bitfield.h>

> +#include <linux/bitops.h>

> +#include <linux/clk.h>

> +#include <linux/clk-provider.h>

> +#include <linux/delay.h>

> +#include <linux/io.h>

> +#include <linux/module.h>

> +#include <linux/nvmem-consumer.h>

> +#include <linux/of.h>

> +#include <linux/of_platform.h>

> +#include <linux/platform_device.h>

> +#include <linux/reset.h>

> +#include <linux/slab.h>

> +

> +#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)


anything with sshphy or hsphy in the name should probably be part of a
PHY driver using drivers/phy/ framework.

> +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);

> +	}

> +}


drivers/regulator maybe?

> +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);

> +}


drivers/reset ?

> +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;


with the stuff that should be using generic frameworks removed, this
looks like dwc3-of-simple.c, which you should be using.

-- 
balbi
Kunihiko Hayashi Jan. 24, 2018, 12:52 p.m. UTC | #2
Hello Felipe,

Thank you for your comments.

On Tue, 23 Jan 2018 15:12:36 +0200 <balbi@kernel.org> wrote:

> 

> Hi,

> 

> Kunihiko Hayashi <hayashi.kunihiko@socionext.com> writes:

> > 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 <hayashi.kunihiko@socionext.com>

> > Signed-off-by: Motoya Tanigawa <tanigawa.motoya@socionext.com>

> > Signed-off-by: Masami Hiramatsu <masami.hiramatsu@linaro.org>

> > ---

> >  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


...snip...

> > +

> > +static void dwc3u_ssphy_testio_write(struct dwc3u_priv *priv, int port,

> > +				     u32 data)

> 

> anything with sshphy or hsphy in the name should probably be part of a

> PHY driver using drivers/phy/ framework.


I can try to separate phy control from this driver.
However, phy registers belongs to "dwc3-glue" IO map area (65b00000),
and this area also contains a reset bit for "dwc3-core" hardware.

Although the phy driver is called from dwc3-core driver,
we need to deassert the reset bit before probing dwc3-core driver.

As shown later, I think that it's difficut to determine the order of
initializing the registers in this area.

> > +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);

> > +	}

> > +}

> 

> drivers/regulator maybe?


VBUS_CONTROL register is used for determing whether "dwc3-glue" hardware
enables vbus connected with "regulator" hardware.

The regulator driver should manage "regulator" hardware, and
I don't think that the driver should manage this register.

> > +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);

> > +}

> 

> drivers/reset ?


The reset driver manages "sysctrl" IO map area in our SoC.

RESET_CTL register belongs to "dwc3-glue" IO map area,
and the kernel can't access this area until enabling usb clocks and
deasserting usb resets in "sysctrl".

I think that "dwc3-glue" register control should be separated from
"sysctrl".

> > +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;

> 

> with the stuff that should be using generic frameworks removed, this

> looks like dwc3-of-simple.c, which you should be using.


Before probing dwc3-core driver and accessing "dwc3-core" register,
we need to do the following items in order:

- enable clock and deassert resets in "sysctrl" with calling the
  clock/reset driver, so that the kernel can access "dwc3-glue"
  IO map area.

- enable vbus and setup phy with the "dwc3-glue" register.

- deassert "dwc3-core" reset with the "dwc3-glue" register,
  so that the kernel can access "dwc3-core" IO map area.

The dwc3-of-simple driver only enables the clock driver before
probing dwc3-core driver, and it seems that the driver have no ways
to deassert the reset driver and manage "dwc3-glue" IO map area.

Are there any ways to manage them before probing dwc3-core driver?

Thank you,

---
Best Regards,
Kunihiko Hayashi
Masahiro Yamada Feb. 26, 2018, 2:48 p.m. UTC | #3
Hi Felipe,


2018-01-24 21:58 GMT+09:00 Felipe Balbi <balbi@kernel.org>:
>

> Hi,

>

> Kunihiko Hayashi <hayashi.kunihiko@socionext.com> writes:

>> Hello Felipe,

>>

>> Thank you for your comments.

>>

>> On Tue, 23 Jan 2018 15:12:36 +0200 <balbi@kernel.org> wrote:

>>

>>>

>>> Hi,

>>>

>>> Kunihiko Hayashi <hayashi.kunihiko@socionext.com> writes:

>>> > 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 <hayashi.kunihiko@socionext.com>

>>> > Signed-off-by: Motoya Tanigawa <tanigawa.motoya@socionext.com>

>>> > Signed-off-by: Masami Hiramatsu <masami.hiramatsu@linaro.org>

>>> > ---

>>> >  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

>>

>> ...snip...

>>

>>> > +

>>> > +static void dwc3u_ssphy_testio_write(struct dwc3u_priv *priv, int port,

>>> > +                               u32 data)

>>>

>>> anything with sshphy or hsphy in the name should probably be part of a

>>> PHY driver using drivers/phy/ framework.

>>

>> I can try to separate phy control from this driver.

>> However, phy registers belongs to "dwc3-glue" IO map area (65b00000),

>> and this area also contains a reset bit for "dwc3-core" hardware.

>>

>> Although the phy driver is called from dwc3-core driver,

>> we need to deassert the reset bit before probing dwc3-core driver.

>>

>> As shown later, I think that it's difficut to determine the order of

>> initializing the registers in this area.

>>

>>> > +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);

>>> > +  }

>>> > +}

>>>

>>> drivers/regulator maybe?

>>

>> VBUS_CONTROL register is used for determing whether "dwc3-glue" hardware

>> enables vbus connected with "regulator" hardware.

>>

>> The regulator driver should manage "regulator" hardware, and

>> I don't think that the driver should manage this register.

>>

>>> > +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);

>>> > +}

>>>

>>> drivers/reset ?

>>

>> The reset driver manages "sysctrl" IO map area in our SoC.

>>

>> RESET_CTL register belongs to "dwc3-glue" IO map area,

>> and the kernel can't access this area until enabling usb clocks and

>> deasserting usb resets in "sysctrl".

>>

>> I think that "dwc3-glue" register control should be separated from

>> "sysctrl".

>

> Just split your address space and treat your glue as a device with

> several children:

>

> glue@65b00000 {

>         compatible = "foo"

>

>         phy@bar {

>                 ...

>         };

>

>         sysctrl@baz {

>                 ...

>         };

>

>         dwc3@foo {

>                 compatible = "snps, dwc3";

>                 ...

>         };

> };

>

> Then you know that you can let dwc3/core.c handle the PHY for you. If we

> need to teach dwc3/core.c about regulators, we can do that. But we don't

> need SoC-specific hacks ;-)

>

> --

> balbi



Slightly related question.


Why don't we put clocks and resets to dwc3/core.c?

dwc3-of-simple.c only handles clocks and resets.
This is generic enough to be added to dwc3/core.c, I think.


I checked the two instances of dwc3-of-simple.

"qcom,dwc3"
https://github.com/torvalds/linux/blob/v4.16-rc3/arch/arm64/boot/dts/qcom/msm8996.dtsi#L780

"rockchip,rk3399-dwc3"
https://github.com/torvalds/linux/blob/v4.16-rc3/arch/arm64/boot/dts/rockchip/rk3399.dtsi#L395


They just contain clocks, resets, and "snps,dwc3" sub-node.


If we do that,

usb@7600000 {
        compatible = "qcom,dwc3";
        clocks = ...;

        dwc3@7600000 {
                compatible = "snps,dwc3";
                reg = ...;
                interrupts = ...;
                phys = ...;
        }
};


will be turned into


dwc3@7600000 {
        compatible = "qcom,dwc3", "snps,dwc3";
        reg = ...;
        clocks = ...;
        interrupts = ...;
        phys = ...;
};


This looks simpler.




Also, dwc3/core.c and dwc3-of-simple.c
duplicate runtime PM.



-- 
Best Regards
Masahiro Yamada
diff mbox series

Patch

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 <hayashi.kunihiko@socionext.com>
+ * Contributors:
+ *      Motoya Tanigawa <tanigawa.motoya@socionext.com>
+ *      Masami Hiramatsu <masami.hiramatsu@linaro.org>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#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 <hayashi.kunihiko@socionext.com>");
+MODULE_DESCRIPTION("DesignWare USB3 UniPhier glue layer");
+MODULE_LICENSE("GPL v2");