All of lore.kernel.org
 help / color / mirror / Atom feed
From: Patrice Chotard <patrice.chotard@st.com>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH v2] phy: add support for STM32 usb phy controller
Date: Fri, 27 Apr 2018 11:01:55 +0200	[thread overview]
Message-ID: <1524819715-23074-1-git-send-email-patrice.chotard@st.com> (raw)

This patch adds phy tranceiver driver for STM32 USB PHY
Controller (usbphyc) that provides dual port High-Speed
phy for OTG (single port) and EHCI/OHCI host controller
(two ports).
One port of the phy is shared between the two USB controllers
through a UTMI+ switch.

Signed-off-by: Christophe Kerello <christophe.kerello@st.com>
Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com>
Signed-off-by: Patrice Chotard <patrice.chotard@st.com>
---

Changes in v2:
- Add bindings documentation

 .../devicetree/bindings/phy/phy-stm32-usbphyc.txt  |  73 ++++
 drivers/phy/Kconfig                                |  13 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/phy-stm32-usbphyc.c                    | 403 +++++++++++++++++++++
 4 files changed, 490 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt
 create mode 100644 drivers/phy/phy-stm32-usbphyc.c

diff --git a/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt
new file mode 100644
index 000000000000..725ae71ae653
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt
@@ -0,0 +1,73 @@
+STMicroelectronics STM32 USB HS PHY controller
+
+The STM32 USBPHYC block contains a dual port High Speed UTMI+ PHY and a UTMI
+switch. It controls PHY configuration and status, and the UTMI+ switch that
+selects either OTG or HOST controller for the second PHY port. It also sets
+PLL configuration.
+
+USBPHYC
+      |_ PLL
+      |
+      |_ PHY port#1 _________________ HOST controller
+      |                    _                 |
+      |                  / 1|________________|
+      |_ PHY port#2 ----|   |________________
+      |                  \_0|                |
+      |_ UTMI switch_______|          OTG controller
+
+
+Phy provider node
+=================
+
+Required properties:
+- compatible: must be "st,stm32mp1-usbphyc"
+- reg: address and length of the usb phy control register set
+- clocks: phandle + clock specifier for the PLL phy clock
+- #address-cells: number of address cells for phys sub-nodes, must be <1>
+- #size-cells: number of size cells for phys sub-nodes, must be <0>
+
+Optional properties:
+- assigned-clocks: phandle + clock specifier for the PLL phy clock
+- assigned-clock-parents: the PLL phy clock parent
+- resets: phandle + reset specifier
+
+Required nodes: one sub-node per port the controller provides.
+
+Phy sub-nodes
+==============
+
+Required properties:
+- reg: phy port index
+- phy-supply: phandle to the regulator providing 3V3 power to the PHY,
+	      see phy-bindings.txt in the same directory.
+- vdda1v1-supply: phandle to the regulator providing 1V1 power to the PHY
+- vdda1v8-supply: phandle to the regulator providing 1V8 power to the PHY
+- #phy-cells: see phy-bindings.txt in the same directory, must be <0> for PHY
+  port#1 and must be <1> for PHY port#2, to select USB controller
+
+
+Example:
+		usbphyc: usb-phy at 5a006000 {
+			compatible = "st,stm32mp1-usbphyc";
+			reg = <0x5a006000 0x1000>;
+			clocks = <&rcc_clk USBPHY_K>;
+			resets = <&rcc_rst USBPHY_R>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			usbphyc_port0: usb-phy at 0 {
+				reg = <0>;
+				phy-supply = <&vdd_usb>;
+				vdda1v1-supply = <&reg11>;
+				vdda1v8-supply = <&reg18>
+				#phy-cells = <0>;
+			};
+
+			usbphyc_port1: usb-phy at 1 {
+				reg = <1>;
+				phy-supply = <&vdd_usb>;
+				vdda1v1-supply = <&reg11>;
+				vdda1v8-supply = <&reg18>
+				#phy-cells = <1>;
+			};
+		};
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 4e9d09910c32..1de3f31bcdd1 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -110,4 +110,17 @@ config STI_USB_PHY
 	  used by USB2 and USB3 Host controllers available on
 	  STiH407 SoC families.
 
+config PHY_STM32_USBPHYC
+	tristate "STMicroelectronics STM32 SoC USB HS PHY driver"
+	depends on PHY && ARCH_STM32MP
+	help
+	  Enable this to support the High-Speed USB transceiver that is part of
+	  STMicroelectronics STM32 SoCs.
+
+	  This driver controls the entire USB PHY block: the USB PHY controller
+	  (USBPHYC) and the two 8-bit wide UTMI+ interface. First interface is
+	  used by an HS USB Host controller, and the second one is shared
+	  between an HS USB OTG controller and an HS USB Host controller,
+	  selected by an USB switch.
+
 endmenu
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 68087ae3b134..e93c3257230d 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_BCM6368_USBH_PHY) += bcm6368-usbh-phy.o
 obj-$(CONFIG_PHY_SANDBOX) += sandbox-phy.o
 obj-$(CONFIG_$(SPL_)PIPE3_PHY) += ti-pipe3-phy.o
 obj-$(CONFIG_STI_USB_PHY) += sti_usb_phy.o
+obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o
diff --git a/drivers/phy/phy-stm32-usbphyc.c b/drivers/phy/phy-stm32-usbphyc.c
new file mode 100644
index 000000000000..744a26c4cd3d
--- /dev/null
+++ b/drivers/phy/phy-stm32-usbphyc.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2018, STMicroelectronics - All Rights Reserved
+ *
+ * SPDX-License-Identifier:	GPL-2.0+	BSD-3-Clause
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <fdtdec.h>
+#include <generic-phy.h>
+#include <reset.h>
+#include <syscon.h>
+#include <usb.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <power/regulator.h>
+
+/* USBPHYC registers */
+#define STM32_USBPHYC_PLL	0x0
+#define STM32_USBPHYC_MISC	0x8
+
+/* STM32_USBPHYC_PLL bit fields */
+#define PLLNDIV			GENMASK(6, 0)
+#define PLLNDIV_SHIFT		0
+#define PLLFRACIN		GENMASK(25, 10)
+#define PLLFRACIN_SHIFT		10
+#define PLLEN			BIT(26)
+#define PLLSTRB			BIT(27)
+#define PLLSTRBYP		BIT(28)
+#define PLLFRACCTL		BIT(29)
+#define PLLDITHEN0		BIT(30)
+#define PLLDITHEN1		BIT(31)
+
+/* STM32_USBPHYC_MISC bit fields */
+#define SWITHOST		BIT(0)
+
+#define MAX_PHYS		2
+
+#define PLL_LOCK_TIME_US	100
+#define PLL_PWR_DOWN_TIME_US	5
+#define PLL_FVCO		2880	 /* in MHz */
+#define PLL_INFF_MIN_RATE	19200000 /* in Hz */
+#define PLL_INFF_MAX_RATE	38400000 /* in Hz */
+
+struct pll_params {
+	u8 ndiv;
+	u16 frac;
+};
+
+struct stm32_usbphyc {
+	fdt_addr_t base;
+	struct clk clk;
+	struct stm32_usbphyc_phy {
+		struct udevice *vdd;
+		struct udevice *vdda1v1;
+		struct udevice *vdda1v8;
+		int index;
+		bool init;
+		bool powered;
+	} phys[MAX_PHYS];
+};
+
+void stm32_usbphyc_get_pll_params(u32 clk_rate, struct pll_params *pll_params)
+{
+	unsigned long long fvco, ndiv, frac;
+
+	/*
+	 *    | FVCO = INFF*2*(NDIV + FRACT/2^16 ) when DITHER_DISABLE[1] = 1
+	 *    | FVCO = 2880MHz
+	 *    | NDIV = integer part of input bits to set the LDF
+	 *    | FRACT = fractional part of input bits to set the LDF
+	 *  =>	PLLNDIV = integer part of (FVCO / (INFF*2))
+	 *  =>	PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16
+	 * <=>  PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16
+	 */
+	fvco = (unsigned long long)PLL_FVCO * 1000000; /* In Hz */
+
+	ndiv = fvco;
+	do_div(ndiv, (clk_rate * 2));
+	pll_params->ndiv = (u8)ndiv;
+
+	frac = fvco * (1 << 16);
+	do_div(frac, (clk_rate * 2));
+	frac = frac - (ndiv * (1 << 16));
+	pll_params->frac = (u16)frac;
+}
+
+static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc)
+{
+	struct pll_params pll_params;
+	u32 clk_rate = clk_get_rate(&usbphyc->clk);
+	u32 usbphyc_pll;
+
+	if ((clk_rate < PLL_INFF_MIN_RATE) || (clk_rate > PLL_INFF_MAX_RATE)) {
+		pr_debug("%s: input clk freq (%dHz) out of range\n",
+			 __func__, clk_rate);
+		return -EINVAL;
+	}
+
+	stm32_usbphyc_get_pll_params(clk_rate, &pll_params);
+
+	usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP;
+	usbphyc_pll |= ((pll_params.ndiv << PLLNDIV_SHIFT) & PLLNDIV);
+
+	if (pll_params.frac) {
+		usbphyc_pll |= PLLFRACCTL;
+		usbphyc_pll |= ((pll_params.frac << PLLFRACIN_SHIFT)
+				 & PLLFRACIN);
+	}
+
+	writel(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL);
+
+	pr_debug("%s: input clk freq=%dHz, ndiv=%d, frac=%d\n", __func__,
+		 clk_rate, pll_params.ndiv, pll_params.frac);
+
+	return 0;
+}
+
+static bool stm32_usbphyc_is_init(struct stm32_usbphyc *usbphyc)
+{
+	int i;
+
+	for (i = 0; i < MAX_PHYS; i++) {
+		if (usbphyc->phys[i].init)
+			return true;
+	}
+
+	return false;
+}
+
+static bool stm32_usbphyc_is_powered(struct stm32_usbphyc *usbphyc)
+{
+	int i;
+
+	for (i = 0; i < MAX_PHYS; i++) {
+		if (usbphyc->phys[i].powered)
+			return true;
+	}
+
+	return false;
+}
+
+static int stm32_usbphyc_phy_init(struct phy *phy)
+{
+	struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev);
+	struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id;
+	bool pllen = readl(usbphyc->base + STM32_USBPHYC_PLL) & PLLEN ?
+		     true : false;
+	int ret;
+
+	pr_debug("%s phy ID = %lu\n", __func__, phy->id);
+	/* Check if one phy port has already configured the pll */
+	if (pllen && stm32_usbphyc_is_init(usbphyc))
+		goto initialized;
+
+	if (pllen) {
+		clrbits_le32(usbphyc->base + STM32_USBPHYC_PLL, PLLEN);
+		udelay(PLL_PWR_DOWN_TIME_US);
+	}
+
+	ret = stm32_usbphyc_pll_init(usbphyc);
+	if (ret)
+		return ret;
+
+	setbits_le32(usbphyc->base + STM32_USBPHYC_PLL, PLLEN);
+
+	/*
+	 * We must wait PLL_LOCK_TIME_US before checking that PLLEN
+	 * bit is still set
+	 */
+	udelay(PLL_LOCK_TIME_US);
+
+	if (!(readl(usbphyc->base + STM32_USBPHYC_PLL) & PLLEN))
+		return -EIO;
+
+initialized:
+	usbphyc_phy->init = true;
+
+	return 0;
+}
+
+static int stm32_usbphyc_phy_exit(struct phy *phy)
+{
+	struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev);
+	struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id;
+
+	pr_debug("%s phy ID = %lu\n", __func__, phy->id);
+	usbphyc_phy->init = false;
+
+	/* Check if other phy port requires pllen */
+	if (stm32_usbphyc_is_init(usbphyc))
+		return 0;
+
+	clrbits_le32(usbphyc->base + STM32_USBPHYC_PLL, PLLEN);
+
+	/*
+	 * We must wait PLL_PWR_DOWN_TIME_US before checking that PLLEN
+	 * bit is still clear
+	 */
+	udelay(PLL_PWR_DOWN_TIME_US);
+
+	if (readl(usbphyc->base + STM32_USBPHYC_PLL) & PLLEN)
+		return -EIO;
+
+	return 0;
+}
+
+static int stm32_usbphyc_phy_power_on(struct phy *phy)
+{
+	struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev);
+	struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id;
+	int ret;
+
+	pr_debug("%s phy ID = %lu\n", __func__, phy->id);
+	if (usbphyc_phy->vdda1v1) {
+		ret = regulator_set_enable(usbphyc_phy->vdda1v1, true);
+		if (ret)
+			return ret;
+	}
+
+	if (usbphyc_phy->vdda1v8) {
+		ret = regulator_set_enable(usbphyc_phy->vdda1v8, true);
+		if (ret)
+			return ret;
+	}
+	if (usbphyc_phy->vdd) {
+		ret = regulator_set_enable(usbphyc_phy->vdd, true);
+		if (ret)
+			return ret;
+	}
+
+	usbphyc_phy->powered = true;
+
+	return 0;
+}
+
+static int stm32_usbphyc_phy_power_off(struct phy *phy)
+{
+	struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev);
+	struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id;
+	int ret;
+
+	pr_debug("%s phy ID = %lu\n", __func__, phy->id);
+	usbphyc_phy->powered = false;
+
+	if (stm32_usbphyc_is_powered(usbphyc))
+		return 0;
+
+	if (usbphyc_phy->vdda1v1) {
+		ret = regulator_set_enable(usbphyc_phy->vdda1v1, false);
+		if (ret)
+			return ret;
+	}
+
+	if (usbphyc_phy->vdda1v8) {
+		ret = regulator_set_enable(usbphyc_phy->vdda1v8, false);
+		if (ret)
+			return ret;
+	}
+
+	if (usbphyc_phy->vdd) {
+		ret = regulator_set_enable(usbphyc_phy->vdd, false);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int stm32_usbphyc_get_regulator(struct udevice *dev, ofnode node,
+				       char *supply_name,
+				       struct udevice **regulator)
+{
+	struct ofnode_phandle_args regulator_phandle;
+	int ret;
+
+	ret = ofnode_parse_phandle_with_args(node, supply_name,
+					     NULL, 0, 0,
+					     &regulator_phandle);
+	if (ret) {
+		dev_err(dev, "Can't find %s property (%d)\n", supply_name, ret);
+		return ret;
+	}
+
+	ret = uclass_get_device_by_ofnode(UCLASS_REGULATOR,
+					  regulator_phandle.node,
+					  regulator);
+
+	if (ret) {
+		dev_err(dev, "Can't get %s regulator (%d)\n", supply_name, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int stm32_usbphyc_of_xlate(struct phy *phy,
+				  struct ofnode_phandle_args *args)
+{
+	if (args->args_count > 1) {
+		pr_debug("%s: invalid args_count: %d\n", __func__,
+			 args->args_count);
+		return -EINVAL;
+	}
+
+	if (args->args[0] >= MAX_PHYS)
+		return -ENODEV;
+
+	if (args->args_count)
+		phy->id = args->args[0];
+	else
+		phy->id = 0;
+
+	return 0;
+}
+
+static const struct phy_ops stm32_usbphyc_phy_ops = {
+	.init = stm32_usbphyc_phy_init,
+	.exit = stm32_usbphyc_phy_exit,
+	.power_on = stm32_usbphyc_phy_power_on,
+	.power_off = stm32_usbphyc_phy_power_off,
+	.of_xlate = stm32_usbphyc_of_xlate,
+};
+
+static int stm32_usbphyc_probe(struct udevice *dev)
+{
+	struct stm32_usbphyc *usbphyc = dev_get_priv(dev);
+	struct reset_ctl reset;
+	ofnode node;
+	int i, ret;
+
+	usbphyc->base = dev_read_addr(dev);
+	if (usbphyc->base == FDT_ADDR_T_NONE)
+		return -EINVAL;
+
+	/* Enable clock */
+	ret = clk_get_by_index(dev, 0, &usbphyc->clk);
+	if (ret)
+		return ret;
+
+	ret = clk_enable(&usbphyc->clk);
+	if (ret)
+		return ret;
+
+	/* Reset */
+	ret = reset_get_by_index(dev, 0, &reset);
+	if (!ret) {
+		reset_assert(&reset);
+		udelay(2);
+		reset_deassert(&reset);
+	}
+
+	/*
+	 * parse all PHY subnodes in order to populate regulator associated
+	 * to each PHY port
+	 */
+	node = dev_read_first_subnode(dev);
+	for (i = 0; i < MAX_PHYS; i++) {
+		struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + i;
+
+		usbphyc_phy->index = i;
+		usbphyc_phy->init = false;
+		usbphyc_phy->powered = false;
+		ret = stm32_usbphyc_get_regulator(dev, node, "phy-supply",
+						  &usbphyc_phy->vdd);
+		if (ret)
+			return ret;
+
+		ret = stm32_usbphyc_get_regulator(dev, node, "vdda1v1-supply",
+						  &usbphyc_phy->vdda1v1);
+		if (ret)
+			return ret;
+
+		ret = stm32_usbphyc_get_regulator(dev, node, "vdda1v8-supply",
+						  &usbphyc_phy->vdda1v8);
+		if (ret)
+			return ret;
+
+		node = dev_read_next_subnode(node);
+	}
+
+	/* Check if second port has to be used for host controller */
+	if (dev_read_bool(dev, "st,port2-switch-to-host"))
+		setbits_le32(usbphyc->base + STM32_USBPHYC_MISC, SWITHOST);
+
+	return 0;
+}
+
+static const struct udevice_id stm32_usbphyc_of_match[] = {
+	{ .compatible = "st,stm32mp1-usbphyc", },
+	{ },
+};
+
+U_BOOT_DRIVER(stm32_usb_phyc) = {
+	.name = "stm32-usbphyc",
+	.id = UCLASS_PHY,
+	.of_match = stm32_usbphyc_of_match,
+	.ops = &stm32_usbphyc_phy_ops,
+	.probe = stm32_usbphyc_probe,
+	.priv_auto_alloc_size = sizeof(struct stm32_usbphyc),
+};
-- 
1.9.1

             reply	other threads:[~2018-04-27  9:01 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-04-27  9:01 Patrice Chotard [this message]
2018-04-27  9:18 ` [U-Boot] [PATCH v2] phy: add support for STM32 usb phy controller Marek Vasut
2018-04-27 14:28   ` Patrice CHOTARD
2018-04-27 14:37     ` Marek Vasut
2018-05-15 13:27   ` Patrice CHOTARD
2018-05-16  9:37     ` Marek Vasut
2018-05-16 14:59       ` Patrice CHOTARD

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1524819715-23074-1-git-send-email-patrice.chotard@st.com \
    --to=patrice.chotard@st.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.