All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Add driver for Broadcom Cygnus USB phy controller
@ 2017-11-08  7:46 ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung
  Cc: devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list, Raveendra Padasalagi

Add driver for Broadcom's USB phy controller's used in Cygnus family
of SoC and it's based on 4.14-rc3 tag.

The patch set can be fetched from iproc-cyg-usb-v2 branch of
https://github.com/Broadcom/arm64-linux.git

Changes since v1:
  - Added "dt-bindings: phy:" subject prefix in dt bindings patch
  - Renamed phy node name from "phy" to "usb-phy"
  - Added BIT() macro definitions for some of the register bit access
  - Added .owner in phy_ops structure.
  - Added cygnus_phy_shutdown_all() to keep probe() clean.
  - Used devm_extcon_register_notifier() instead of extcon_register_notifier()

Raveendra Padasalagi (3):
  dt-bindings: phy: Add Cygnus usb phy binding
  drivers: phy: broadcom: Add driver for Cygnus USB phy controller
  ARM: dts: Add dt node for Broadcom Cygnus USB phy

 .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 ++++
 arch/arm/boot/dts/bcm-cygnus.dtsi                  |  35 ++
 drivers/phy/broadcom/Kconfig                       |  14 +
 drivers/phy/broadcom/Makefile                      |   1 +
 drivers/phy/broadcom/phy-bcm-cygnus-usb.c          | 690 +++++++++++++++++++++
 5 files changed, 846 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
 create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c

-- 
1.9.1

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 0/3] Add driver for Broadcom Cygnus USB phy controller
@ 2017-11-08  7:46 ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: linux-arm-kernel

Add driver for Broadcom's USB phy controller's used in Cygnus family
of SoC and it's based on 4.14-rc3 tag.

The patch set can be fetched from iproc-cyg-usb-v2 branch of
https://github.com/Broadcom/arm64-linux.git

Changes since v1:
  - Added "dt-bindings: phy:" subject prefix in dt bindings patch
  - Renamed phy node name from "phy" to "usb-phy"
  - Added BIT() macro definitions for some of the register bit access
  - Added .owner in phy_ops structure.
  - Added cygnus_phy_shutdown_all() to keep probe() clean.
  - Used devm_extcon_register_notifier() instead of extcon_register_notifier()

Raveendra Padasalagi (3):
  dt-bindings: phy: Add Cygnus usb phy binding
  drivers: phy: broadcom: Add driver for Cygnus USB phy controller
  ARM: dts: Add dt node for Broadcom Cygnus USB phy

 .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 ++++
 arch/arm/boot/dts/bcm-cygnus.dtsi                  |  35 ++
 drivers/phy/broadcom/Kconfig                       |  14 +
 drivers/phy/broadcom/Makefile                      |   1 +
 drivers/phy/broadcom/phy-bcm-cygnus-usb.c          | 690 +++++++++++++++++++++
 5 files changed, 846 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
 create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c

-- 
1.9.1

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
  2017-11-08  7:46 ` Raveendra Padasalagi
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  -1 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung
  Cc: devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list, Raveendra Padasalagi

Add devicetree binding document for broadcom's
Cygnus SoC specific usb phy controller driver.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
---
 .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
 1 file changed, 106 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt

diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
new file mode 100644
index 0000000..bbc4b94
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
@@ -0,0 +1,106 @@
+BROADCOM CYGNUS USB PHY
+
+Required Properties:
+- compatible:  brcm,cygnus-usb-phy
+- reg : the register start address and length for
+        crmu_usbphy_aon_ctrl,
+        cdru usb phy control,
+        usb host idm registers,
+        usb device idm registers.
+- reg-names: a list of the names corresponding to the previous register ranges
+  Should contain
+        "crmu-usbphy-aon-ctrl",
+        "cdru-usbphy",
+        "usb2h-idm",
+        "usb2d-idm".
+- address-cells: should be 1
+- size-cells: should be 0
+
+Sub-nodes:
+  Each port's PHY should be represented as a sub-node.
+
+Sub-nodes required properties:
+- reg: the PHY number
+- #phy-cells must be 1
+  The node that uses the phy must provide 1 integer argument specifying
+  port number.
+
+Optional Properties:
+- vbus-p#-supply : The regulator for vbus out control for the host
+  functionality enabled ports.
+- vbus-gpios: vbus gpio binding
+  This is mandatory for port 2, as port 2 is used as dual role phy.
+  Based on the vbus and id values device or host role is determined
+  for phy 2.
+
+- extcon: extcon phandle
+  This is mandatory for port 2,  as port 2 is used as dual role phy.
+  extcon should be phandle to external usb gpio module which provide
+  device or host role notifications based on the ID and VBUS gpio's state.
+
+
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+NOTE: port 0 and port 1 are host only and port 2 is dual role port.
+
+Example of phy :
+	usbphy: usb-phy@0301c028 {
+		compatible = "brcm,cygnus-usb-phy";
+		reg = <0x0301c028 0x4>,
+		      <0x0301d1b4 0x5c>,
+		      <0x18115000 0xa00>,
+		      <0x18111000 0xa00>;
+		reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
+			    "usb2h-idm", "usb2d-idm";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		usbphy0: usb-phy@0 {
+			reg = <0>;
+			#phy-cells = <1>;
+		};
+
+		usbphy1: usb-phy@1 {
+			reg = <1>;
+			#phy-cells = <1>;
+		};
+
+		usbphy2: usb-phy@2 {
+			reg = <2>;
+			#phy-cells = <1>;
+			extcon = <&extcon_usb>;
+		};
+	};
+
+	extcon_usb: extcon_usb {
+		compatible = "linux,extcon-usb-gpio";
+		vbus-gpio = <&gpio_asiu 121 0>;
+		id-gpio = <&gpio_asiu 122 0>;
+		status = "okay";
+	};
+
+
+Example of node using the phy:
+
+	/* This nodes declares all three ports, port 0
+	and port 1 are host and port 2 is device or host */
+
+	ehci0: usb@18048000 {
+		compatible = "generic-ehci";
+		reg = <0x18048000 0x100>;
+		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
+		phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
+		phy-names = "usbp0","usbp1","usbp2";
+		status = "okay";
+	};
+
+	/* This node declares port 2 phy
+	and configures it for device */
+
+	usbd_udc_dwc1: usb@1804c000 {
+		compatible = "iproc-udc";
+		reg = <0x1804c000 0x2000>;
+		interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+		phys = <&usbphy2 2>;
+		phy-names = "usbdrd";
+	};
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: linux-arm-kernel

Add devicetree binding document for broadcom's
Cygnus SoC specific usb phy controller driver.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
---
 .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
 1 file changed, 106 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt

diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
new file mode 100644
index 0000000..bbc4b94
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
@@ -0,0 +1,106 @@
+BROADCOM CYGNUS USB PHY
+
+Required Properties:
+- compatible:  brcm,cygnus-usb-phy
+- reg : the register start address and length for
+        crmu_usbphy_aon_ctrl,
+        cdru usb phy control,
+        usb host idm registers,
+        usb device idm registers.
+- reg-names: a list of the names corresponding to the previous register ranges
+  Should contain
+        "crmu-usbphy-aon-ctrl",
+        "cdru-usbphy",
+        "usb2h-idm",
+        "usb2d-idm".
+- address-cells: should be 1
+- size-cells: should be 0
+
+Sub-nodes:
+  Each port's PHY should be represented as a sub-node.
+
+Sub-nodes required properties:
+- reg: the PHY number
+- #phy-cells must be 1
+  The node that uses the phy must provide 1 integer argument specifying
+  port number.
+
+Optional Properties:
+- vbus-p#-supply : The regulator for vbus out control for the host
+  functionality enabled ports.
+- vbus-gpios: vbus gpio binding
+  This is mandatory for port 2, as port 2 is used as dual role phy.
+  Based on the vbus and id values device or host role is determined
+  for phy 2.
+
+- extcon: extcon phandle
+  This is mandatory for port 2,  as port 2 is used as dual role phy.
+  extcon should be phandle to external usb gpio module which provide
+  device or host role notifications based on the ID and VBUS gpio's state.
+
+
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+NOTE: port 0 and port 1 are host only and port 2 is dual role port.
+
+Example of phy :
+	usbphy: usb-phy at 0301c028 {
+		compatible = "brcm,cygnus-usb-phy";
+		reg = <0x0301c028 0x4>,
+		      <0x0301d1b4 0x5c>,
+		      <0x18115000 0xa00>,
+		      <0x18111000 0xa00>;
+		reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
+			    "usb2h-idm", "usb2d-idm";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		usbphy0: usb-phy at 0 {
+			reg = <0>;
+			#phy-cells = <1>;
+		};
+
+		usbphy1: usb-phy at 1 {
+			reg = <1>;
+			#phy-cells = <1>;
+		};
+
+		usbphy2: usb-phy at 2 {
+			reg = <2>;
+			#phy-cells = <1>;
+			extcon = <&extcon_usb>;
+		};
+	};
+
+	extcon_usb: extcon_usb {
+		compatible = "linux,extcon-usb-gpio";
+		vbus-gpio = <&gpio_asiu 121 0>;
+		id-gpio = <&gpio_asiu 122 0>;
+		status = "okay";
+	};
+
+
+Example of node using the phy:
+
+	/* This nodes declares all three ports, port 0
+	and port 1 are host and port 2 is device or host */
+
+	ehci0: usb at 18048000 {
+		compatible = "generic-ehci";
+		reg = <0x18048000 0x100>;
+		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
+		phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
+		phy-names = "usbp0","usbp1","usbp2";
+		status = "okay";
+	};
+
+	/* This node declares port 2 phy
+	and configures it for device */
+
+	usbd_udc_dwc1: usb at 1804c000 {
+		compatible = "iproc-udc";
+		reg = <0x1804c000 0x2000>;
+		interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
+		phys = <&usbphy2 2>;
+		phy-names = "usbdrd";
+	};
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung
  Cc: devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list, Raveendra Padasalagi

Add driver for Broadcom's USB phy controller's used in Cygnus
familyof SoC. Cygnus has three USB phy controller's, port 0,
port 1 provides USB host functionality and port 2 can be configured
for host/device role.

Configuration of host/device role for port 2 is achieved based on
the extcon events, the driver registers to extcon framework to get
appropriate connect events for Host/Device cables connect/disconnect
states based on VBUS and ID interrupts.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
---
 drivers/phy/broadcom/Kconfig              |  14 +
 drivers/phy/broadcom/Makefile             |   1 +
 drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
 3 files changed, 705 insertions(+)
 create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c

diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
index 64fc59c..3179daf 100644
--- a/drivers/phy/broadcom/Kconfig
+++ b/drivers/phy/broadcom/Kconfig
@@ -1,6 +1,20 @@
 #
 # Phy drivers for Broadcom platforms
 #
+config PHY_BCM_CYGNUS_USB
+	tristate "Broadcom Cygnus USB PHY support"
+	depends on OF
+	depends on ARCH_BCM_CYGNUS || COMPILE_TEST
+	select GENERIC_PHY
+	select EXTCON_USB_GPIO
+	default ARCH_BCM_CYGNUS
+	help
+	  Enable this to support three USB PHY's present in Broadcom's
+	  Cygnus chip.
+
+	  The phys are capable of supporting host mode on all ports and
+	  device mode for port 2.
+
 config PHY_CYGNUS_PCIE
 	tristate "Broadcom Cygnus PCIe PHY driver"
 	depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
index 4eb82ec..3dec23c 100644
--- a/drivers/phy/broadcom/Makefile
+++ b/drivers/phy/broadcom/Makefile
@@ -1,4 +1,5 @@
 obj-$(CONFIG_PHY_CYGNUS_PCIE)		+= phy-bcm-cygnus-pcie.o
+obj-$(CONFIG_PHY_BCM_CYGNUS_USB)	+= phy-bcm-cygnus-usb.o
 obj-$(CONFIG_BCM_KONA_USB2_PHY)		+= phy-bcm-kona-usb2.o
 obj-$(CONFIG_PHY_BCM_NS_USB2)		+= phy-bcm-ns-usb2.o
 obj-$(CONFIG_PHY_BCM_NS_USB3)		+= phy-bcm-ns-usb3.o
diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
new file mode 100644
index 0000000..cf957d4
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
@@ -0,0 +1,690 @@
+/*
+ * Copyright 2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation (the "GPL").
+ *
+ * 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 version 2 (GPLv2) for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 (GPLv2) along with this source code.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/extcon.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+
+/* CDRU Block Register Offsets and bit definitions */
+#define CDRU_USBPHY_CLK_RST_SEL_OFFSET			0x0
+#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET		0x4
+#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET		0x5C
+#define CDRU_USBPHY_P0_STATUS_OFFSET			0x1C
+#define CDRU_USBPHY_P1_STATUS_OFFSET			0x34
+#define CDRU_USBPHY_P2_STATUS_OFFSET			0x4C
+
+#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG			BIT(1)
+#define CDRU_USBPHY_USBPHY_PLL_LOCK			BIT(0)
+#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE	BIT(0)
+
+/* CRMU Block Register Offsets and bit definitions */
+#define CRMU_USB_PHY_AON_CTRL_OFFSET			0x0
+#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC			BIT(1)
+#define CRMU_USBPHY_P0_RESETB				BIT(2)
+#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC			BIT(9)
+#define CRMU_USBPHY_P1_RESETB				BIT(10)
+#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC			BIT(17)
+#define CRMU_USBPHY_P2_RESETB				BIT(18)
+
+/* USB2 IDM Block register Offset and bit definitions */
+#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET		0x0408
+#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE	BIT(0)
+#define SUSPEND_OVERRIDE_0				BIT(13)
+#define SUSPEND_OVERRIDE_0_POS				13
+#define SUSPEND_OVERRIDE_1				BIT(14)
+#define SUSPEND_OVERRIDE_1_POS				14
+#define SUSPEND_OVERRIDE_2				BIT(15)
+#define SUSPEND_OVERRIDE_2_POS				15
+
+#define USB2_IDM_IDM_RESET_CONTROL_OFFSET		0x0800
+#define USB2_IDM_IDM_RESET_CONTROL__RESET		BIT(0)
+#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I	BIT(24)
+
+#define PHY2_DEV_HOST_CTRL_SEL_DEVICE			0
+#define PHY2_DEV_HOST_CTRL_SEL_HOST			1
+#define PHY2_DEV_HOST_CTRL_SEL_IDLE			2
+
+#define PLL_LOCK_RETRY_COUNT				1000
+#define MAX_REGULATOR_NAME_LEN				25
+#define DUAL_ROLE_PHY					2
+
+#define USBPHY_WQ_DELAY_MS		msecs_to_jiffies(500)
+#define USB2_SEL_DEVICE			0
+#define USB2_SEL_HOST			1
+#define USB2_SEL_IDLE			2
+#define USB_CONNECTED			1
+#define USB_DISCONNECTED		0
+#define MAX_NUM_PHYS			3
+
+static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
+				CDRU_USBPHY_P1_STATUS_OFFSET,
+				CDRU_USBPHY_P2_STATUS_OFFSET};
+
+struct cygnus_phy_instance;
+
+struct cygnus_phy_driver {
+	void __iomem *cdru_usbphy_regs;
+	void __iomem *crmu_usbphy_aon_ctrl_regs;
+	void __iomem *usb2h_idm_regs;
+	void __iomem *usb2d_idm_regs;
+	int num_phys;
+	bool idm_host_enabled;
+	struct cygnus_phy_instance *instances;
+	int phyto_src_clk;
+	struct platform_device *pdev;
+};
+
+struct cygnus_phy_instance {
+	struct cygnus_phy_driver *driver;
+	struct phy *generic_phy;
+	int port;
+	int new_state;		/* 1 - Host, 0 - device, 2 - idle*/
+	bool power;		/* 1 - powered_on 0 - powered off */
+	struct regulator *vbus_supply;
+	spinlock_t lock;
+	struct extcon_dev *edev;
+	struct notifier_block	device_nb;
+	struct notifier_block	host_nb;
+};
+
+static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
+				    struct cygnus_phy_driver *phy_driver)
+{
+	/* Wait for the PLL lock status */
+	int retry = PLL_LOCK_RETRY_COUNT;
+	u32 reg_val;
+
+	do {
+		udelay(1);
+		reg_val = readl(phy_driver->cdru_usbphy_regs +
+				usb_reg);
+		if (reg_val & bit_mask)
+			return 0;
+	} while (--retry > 0);
+
+	return -EBUSY;
+}
+
+static struct phy *cygnus_phy_xlate(struct device *dev,
+				    struct of_phandle_args *args)
+{
+	struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
+	struct cygnus_phy_instance *instance_ptr;
+
+	if (!phy_driver)
+		return ERR_PTR(-EINVAL);
+
+	if (WARN_ON(args->args[0] >= phy_driver->num_phys))
+		return ERR_PTR(-ENODEV);
+
+	if (WARN_ON(args->args_count < 1))
+		return ERR_PTR(-EINVAL);
+
+	instance_ptr = &phy_driver->instances[args->args[0]];
+	if (instance_ptr->port > phy_driver->phyto_src_clk)
+		phy_driver->phyto_src_clk = instance_ptr->port;
+
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		goto ret_p2;
+	phy_driver->instances[instance_ptr->port].new_state =
+						PHY2_DEV_HOST_CTRL_SEL_HOST;
+
+ret_p2:
+	return phy_driver->instances[instance_ptr->port].generic_phy;
+}
+
+static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
+{
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	reg_val = readl(phy_driver->usb2d_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+	if (enable)
+		reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+	else
+		reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+
+	writel(reg_val, phy_driver->usb2d_idm_regs +
+		USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+	reg_val = readl(phy_driver->usb2d_idm_regs +
+			USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+
+	if (enable)
+		reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
+	else
+		reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
+
+	writel(reg_val, phy_driver->usb2d_idm_regs +
+		USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+}
+
+static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
+					    u32 src_phy)
+{
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	writel(src_phy, phy_driver->cdru_usbphy_regs +
+		CDRU_USBPHY_CLK_RST_SEL_OFFSET);
+
+	/* Force the clock/reset source phy to not suspend */
+	reg_val = readl(phy_driver->usb2h_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+	reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
+			SUSPEND_OVERRIDE_2);
+
+	reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
+
+	writel(reg_val, phy_driver->usb2h_idm_regs +
+		USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+}
+
+static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
+{
+	u32 reg_val;
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
+		writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
+			phy_driver->cdru_usbphy_regs +
+			CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
+		return;
+	}
+
+	/*
+	 * Disable suspend/resume signals to device controller
+	 * when a port is in device mode
+	 */
+	writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
+		phy_driver->cdru_usbphy_regs +
+		CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
+	reg_val = readl(phy_driver->cdru_usbphy_regs +
+			CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
+	reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
+	writel(reg_val, phy_driver->cdru_usbphy_regs +
+			CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
+}
+
+static int cygnus_phy_init(struct phy *generic_phy)
+{
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_phy_dual_role_init(instance_ptr);
+
+	return 0;
+}
+
+static int cygnus_phy_shutdown(struct phy *generic_phy)
+{
+	u32 reg_val;
+	int i, ret;
+	unsigned long flags;
+	bool power_off_flag = true;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	if (instance_ptr->vbus_supply) {
+		ret = regulator_disable(instance_ptr->vbus_supply);
+		if (ret) {
+			dev_err(&generic_phy->dev,
+					"Failed to disable regulator\n");
+			return ret;
+		}
+	}
+
+	spin_lock_irqsave(&instance_ptr->lock, flags);
+
+	/* power down the phy */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	if (instance_ptr->port == 0) {
+		reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P0_RESETB;
+	} else if (instance_ptr->port == 1) {
+		reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P1_RESETB;
+	} else if (instance_ptr->port == 2) {
+		reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P2_RESETB;
+	}
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+
+	instance_ptr->power = false;
+
+	/* Determine whether all the phy's are powered off */
+	for (i = 0; i < phy_driver->num_phys; i++) {
+		if (phy_driver->instances[i].power == true) {
+			power_off_flag = false;
+			break;
+		}
+	}
+
+	/* Disable clock to USB device and keep the USB device in reset */
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
+					    false);
+
+	/*
+	 * Put the host controller into reset state and
+	 * disable clock if all the phy's are powered off
+	 */
+	if (power_off_flag) {
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+		reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				 USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		phy_driver->idm_host_enabled = false;
+	}
+	spin_unlock_irqrestore(&instance_ptr->lock, flags);
+	return 0;
+}
+
+static int cygnus_phy_poweron(struct phy *generic_phy)
+{
+	int ret;
+	unsigned long flags;
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+	u32 extcon_event = instance_ptr->new_state;
+
+	/*
+	 * Switch on the regulator only if in HOST mode
+	 */
+	if (instance_ptr->vbus_supply) {
+		ret = regulator_enable(instance_ptr->vbus_supply);
+		if (ret) {
+			dev_err(&generic_phy->dev,
+				"Failed to enable regulator\n");
+			goto err_shutdown;
+		}
+	}
+
+	spin_lock_irqsave(&instance_ptr->lock, flags);
+	/* Bring the AFE block out of reset to start powering up the PHY */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	if (instance_ptr->port == 0)
+		reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+	else if (instance_ptr->port == 1)
+		reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+	else if (instance_ptr->port == 2)
+		reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+
+	/* Check for power on and PLL lock */
+	ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
+				CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
+	if (ret < 0) {
+		dev_err(&generic_phy->dev,
+			"Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
+			instance_ptr->port);
+		spin_unlock_irqrestore(&instance_ptr->lock, flags);
+		goto err_shutdown;
+	}
+	ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
+				CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
+	if (ret < 0) {
+		dev_err(&generic_phy->dev,
+			"Timed out waiting for USBPHY_PLL_LOCK on port %d",
+			instance_ptr->port);
+		spin_unlock_irqrestore(&instance_ptr->lock, flags);
+		goto err_shutdown;
+	}
+
+	instance_ptr->power = true;
+
+	/* Enable clock to USB device and take the USB device out of reset */
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
+
+	/* Set clock source provider to be the last powered on phy */
+	if (instance_ptr->port == phy_driver->phyto_src_clk)
+		cygnus_phy_clk_reset_src_switch(generic_phy,
+				instance_ptr->port);
+
+	if (phy_driver->idm_host_enabled != true &&
+		extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
+		/* Enable clock to USB host and take the host out of reset */
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+		reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				 USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		phy_driver->idm_host_enabled = true;
+	}
+	spin_unlock_irqrestore(&instance_ptr->lock, flags);
+
+	return 0;
+err_shutdown:
+	cygnus_phy_shutdown(generic_phy);
+	return ret;
+}
+
+static int usbd_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct cygnus_phy_instance *instance_ptr = container_of(self,
+					struct cygnus_phy_instance, device_nb);
+
+	if (event) {
+		instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
+		cygnus_phy_dual_role_init(instance_ptr);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int usbh_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct cygnus_phy_instance *instance_ptr = container_of(self,
+					struct cygnus_phy_instance, host_nb);
+	if (event) {
+		instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
+		cygnus_phy_dual_role_init(instance_ptr);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int cygnus_register_extcon_notifiers(
+				struct cygnus_phy_instance *instance_ptr)
+{
+	int ret = 0;
+	struct device *dev = &instance_ptr->generic_phy->dev;
+
+	if (of_property_read_bool(dev->of_node, "extcon")) {
+		instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
+		if (IS_ERR(instance_ptr->edev)) {
+			if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
+				return -EPROBE_DEFER;
+			ret = PTR_ERR(instance_ptr->edev);
+			goto err;
+		}
+
+		instance_ptr->device_nb.notifier_call = usbd_connect_notify;
+		ret = devm_extcon_register_notifier(dev,
+						instance_ptr->edev,
+						EXTCON_USB,
+						&instance_ptr->device_nb);
+		if (ret < 0) {
+			dev_err(dev, "Can't register extcon notifier\n");
+			goto err;
+		}
+
+		if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
+			instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
+			cygnus_phy_dual_role_init(instance_ptr);
+		}
+
+		instance_ptr->host_nb.notifier_call = usbh_connect_notify;
+		ret = devm_extcon_register_notifier(dev,
+						instance_ptr->edev,
+						EXTCON_USB_HOST,
+						&instance_ptr->host_nb);
+		if (ret < 0) {
+			dev_err(dev, "Can't register extcon notifier\n");
+			goto err;
+		}
+
+		if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
+					== true) {
+			instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
+			cygnus_phy_dual_role_init(instance_ptr);
+		}
+	} else {
+		dev_err(dev, "Extcon device handle not found\n");
+		return -EINVAL;
+	}
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct phy_ops ops = {
+	.init		= cygnus_phy_init,
+	.power_on	= cygnus_phy_poweron,
+	.power_off	= cygnus_phy_shutdown,
+	.owner = THIS_MODULE,
+};
+
+static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
+{
+	u32 reg_val;
+
+	/*
+	 * Shutdown all ports. They can be powered up as
+	 * required
+	 */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P0_RESETB;
+	reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P1_RESETB;
+	reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P2_RESETB;
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+}
+
+static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
+{
+	struct device_node *child;
+	char *vbus_name;
+	struct platform_device *pdev = phy_driver->pdev;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct cygnus_phy_instance *instance_ptr;
+	unsigned int id, ret;
+
+	for_each_available_child_of_node(node, child) {
+		if (of_property_read_u32(child, "reg", &id)) {
+			dev_err(dev, "missing reg property for %s\n",
+				child->name);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		if (id >= MAX_NUM_PHYS) {
+			dev_err(dev, "invalid PHY id: %u\n", id);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		instance_ptr = &phy_driver->instances[id];
+		instance_ptr->driver = phy_driver;
+		instance_ptr->port = id;
+		spin_lock_init(&instance_ptr->lock);
+
+		if (instance_ptr->generic_phy) {
+			dev_err(dev, "duplicated PHY id: %u\n", id);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		instance_ptr->generic_phy =
+				devm_phy_create(dev, child, &ops);
+		if (IS_ERR(instance_ptr->generic_phy)) {
+			dev_err(dev, "Failed to create usb phy %d", id);
+			ret = PTR_ERR(instance_ptr->generic_phy);
+			goto put_child;
+		}
+
+		vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
+					MAX_REGULATOR_NAME_LEN,
+					GFP_KERNEL);
+		if (!vbus_name) {
+			ret = -ENOMEM;
+			goto put_child;
+		}
+
+		/* regulator use is optional */
+		sprintf(vbus_name, "vbus-p%d", id);
+		instance_ptr->vbus_supply =
+			devm_regulator_get(&instance_ptr->generic_phy->dev,
+					vbus_name);
+		if (IS_ERR(instance_ptr->vbus_supply))
+			instance_ptr->vbus_supply = NULL;
+		devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
+		phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
+	}
+
+	return 0;
+
+put_child:
+	of_node_put(child);
+	return ret;
+}
+
+static int cygnus_phy_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct cygnus_phy_driver *phy_driver;
+	struct phy_provider *phy_provider;
+	int i, ret;
+	u32 reg_val;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+
+	/* allocate memory for each phy instance */
+	phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
+				  GFP_KERNEL);
+	if (!phy_driver)
+		return -ENOMEM;
+
+	phy_driver->num_phys = of_get_child_count(node);
+
+	if (phy_driver->num_phys == 0) {
+		dev_err(dev, "PHY no child node\n");
+		return -ENODEV;
+	}
+
+	phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
+					     sizeof(struct cygnus_phy_instance),
+					     GFP_KERNEL);
+	if (!phy_driver->instances)
+		return -ENOMEM;
+
+	phy_driver->pdev = pdev;
+	platform_set_drvdata(pdev, phy_driver);
+
+	ret = cygnus_phy_instance_create(phy_driver);
+	if (ret)
+		return ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "crmu-usbphy-aon-ctrl");
+	phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
+		return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "cdru-usbphy");
+	phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->cdru_usbphy_regs))
+		return PTR_ERR(phy_driver->cdru_usbphy_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
+	phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->usb2h_idm_regs))
+		return PTR_ERR(phy_driver->usb2h_idm_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
+	phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->usb2d_idm_regs))
+		return PTR_ERR(phy_driver->usb2d_idm_regs);
+
+	reg_val = readl(phy_driver->usb2d_idm_regs);
+	writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
+		   phy_driver->usb2d_idm_regs);
+	phy_driver->idm_host_enabled = false;
+
+	/* Shutdown all ports. They can be powered up as required */
+	cygnus_phy_shutdown_all(phy_driver);
+
+	phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
+	if (IS_ERR(phy_provider)) {
+		dev_err(dev, "Failed to register as phy provider\n");
+		ret = PTR_ERR(phy_provider);
+		return ret;
+	}
+
+	for (i = 0; i < phy_driver->num_phys; i++) {
+		if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
+			ret = cygnus_register_extcon_notifiers(
+				&phy_driver->instances[DUAL_ROLE_PHY]);
+			if (ret) {
+				dev_err(dev, "Failed to register notifier\n");
+				return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id cygnus_phy_dt_ids[] = {
+	{ .compatible = "brcm,cygnus-usb-phy", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
+
+static struct platform_driver cygnus_phy_driver = {
+	.probe = cygnus_phy_probe,
+	.driver = {
+		.name = "bcm-cygnus-usbphy",
+		.of_match_table = of_match_ptr(cygnus_phy_dt_ids),
+	},
+};
+module_platform_driver(cygnus_phy_driver);
+
+MODULE_ALIAS("platform:bcm-cygnus-usbphy");
+MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
+MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
	Raveendra Padasalagi

Add driver for Broadcom's USB phy controller's used in Cygnus
familyof SoC. Cygnus has three USB phy controller's, port 0,
port 1 provides USB host functionality and port 2 can be configured
for host/device role.

Configuration of host/device role for port 2 is achieved based on
the extcon events, the driver registers to extcon framework to get
appropriate connect events for Host/Device cables connect/disconnect
states based on VBUS and ID interrupts.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
 drivers/phy/broadcom/Kconfig              |  14 +
 drivers/phy/broadcom/Makefile             |   1 +
 drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
 3 files changed, 705 insertions(+)
 create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c

diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
index 64fc59c..3179daf 100644
--- a/drivers/phy/broadcom/Kconfig
+++ b/drivers/phy/broadcom/Kconfig
@@ -1,6 +1,20 @@
 #
 # Phy drivers for Broadcom platforms
 #
+config PHY_BCM_CYGNUS_USB
+	tristate "Broadcom Cygnus USB PHY support"
+	depends on OF
+	depends on ARCH_BCM_CYGNUS || COMPILE_TEST
+	select GENERIC_PHY
+	select EXTCON_USB_GPIO
+	default ARCH_BCM_CYGNUS
+	help
+	  Enable this to support three USB PHY's present in Broadcom's
+	  Cygnus chip.
+
+	  The phys are capable of supporting host mode on all ports and
+	  device mode for port 2.
+
 config PHY_CYGNUS_PCIE
 	tristate "Broadcom Cygnus PCIe PHY driver"
 	depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
index 4eb82ec..3dec23c 100644
--- a/drivers/phy/broadcom/Makefile
+++ b/drivers/phy/broadcom/Makefile
@@ -1,4 +1,5 @@
 obj-$(CONFIG_PHY_CYGNUS_PCIE)		+= phy-bcm-cygnus-pcie.o
+obj-$(CONFIG_PHY_BCM_CYGNUS_USB)	+= phy-bcm-cygnus-usb.o
 obj-$(CONFIG_BCM_KONA_USB2_PHY)		+= phy-bcm-kona-usb2.o
 obj-$(CONFIG_PHY_BCM_NS_USB2)		+= phy-bcm-ns-usb2.o
 obj-$(CONFIG_PHY_BCM_NS_USB3)		+= phy-bcm-ns-usb3.o
diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
new file mode 100644
index 0000000..cf957d4
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
@@ -0,0 +1,690 @@
+/*
+ * Copyright 2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation (the "GPL").
+ *
+ * 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 version 2 (GPLv2) for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 (GPLv2) along with this source code.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/extcon.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+
+/* CDRU Block Register Offsets and bit definitions */
+#define CDRU_USBPHY_CLK_RST_SEL_OFFSET			0x0
+#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET		0x4
+#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET		0x5C
+#define CDRU_USBPHY_P0_STATUS_OFFSET			0x1C
+#define CDRU_USBPHY_P1_STATUS_OFFSET			0x34
+#define CDRU_USBPHY_P2_STATUS_OFFSET			0x4C
+
+#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG			BIT(1)
+#define CDRU_USBPHY_USBPHY_PLL_LOCK			BIT(0)
+#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE	BIT(0)
+
+/* CRMU Block Register Offsets and bit definitions */
+#define CRMU_USB_PHY_AON_CTRL_OFFSET			0x0
+#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC			BIT(1)
+#define CRMU_USBPHY_P0_RESETB				BIT(2)
+#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC			BIT(9)
+#define CRMU_USBPHY_P1_RESETB				BIT(10)
+#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC			BIT(17)
+#define CRMU_USBPHY_P2_RESETB				BIT(18)
+
+/* USB2 IDM Block register Offset and bit definitions */
+#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET		0x0408
+#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE	BIT(0)
+#define SUSPEND_OVERRIDE_0				BIT(13)
+#define SUSPEND_OVERRIDE_0_POS				13
+#define SUSPEND_OVERRIDE_1				BIT(14)
+#define SUSPEND_OVERRIDE_1_POS				14
+#define SUSPEND_OVERRIDE_2				BIT(15)
+#define SUSPEND_OVERRIDE_2_POS				15
+
+#define USB2_IDM_IDM_RESET_CONTROL_OFFSET		0x0800
+#define USB2_IDM_IDM_RESET_CONTROL__RESET		BIT(0)
+#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I	BIT(24)
+
+#define PHY2_DEV_HOST_CTRL_SEL_DEVICE			0
+#define PHY2_DEV_HOST_CTRL_SEL_HOST			1
+#define PHY2_DEV_HOST_CTRL_SEL_IDLE			2
+
+#define PLL_LOCK_RETRY_COUNT				1000
+#define MAX_REGULATOR_NAME_LEN				25
+#define DUAL_ROLE_PHY					2
+
+#define USBPHY_WQ_DELAY_MS		msecs_to_jiffies(500)
+#define USB2_SEL_DEVICE			0
+#define USB2_SEL_HOST			1
+#define USB2_SEL_IDLE			2
+#define USB_CONNECTED			1
+#define USB_DISCONNECTED		0
+#define MAX_NUM_PHYS			3
+
+static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
+				CDRU_USBPHY_P1_STATUS_OFFSET,
+				CDRU_USBPHY_P2_STATUS_OFFSET};
+
+struct cygnus_phy_instance;
+
+struct cygnus_phy_driver {
+	void __iomem *cdru_usbphy_regs;
+	void __iomem *crmu_usbphy_aon_ctrl_regs;
+	void __iomem *usb2h_idm_regs;
+	void __iomem *usb2d_idm_regs;
+	int num_phys;
+	bool idm_host_enabled;
+	struct cygnus_phy_instance *instances;
+	int phyto_src_clk;
+	struct platform_device *pdev;
+};
+
+struct cygnus_phy_instance {
+	struct cygnus_phy_driver *driver;
+	struct phy *generic_phy;
+	int port;
+	int new_state;		/* 1 - Host, 0 - device, 2 - idle*/
+	bool power;		/* 1 - powered_on 0 - powered off */
+	struct regulator *vbus_supply;
+	spinlock_t lock;
+	struct extcon_dev *edev;
+	struct notifier_block	device_nb;
+	struct notifier_block	host_nb;
+};
+
+static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
+				    struct cygnus_phy_driver *phy_driver)
+{
+	/* Wait for the PLL lock status */
+	int retry = PLL_LOCK_RETRY_COUNT;
+	u32 reg_val;
+
+	do {
+		udelay(1);
+		reg_val = readl(phy_driver->cdru_usbphy_regs +
+				usb_reg);
+		if (reg_val & bit_mask)
+			return 0;
+	} while (--retry > 0);
+
+	return -EBUSY;
+}
+
+static struct phy *cygnus_phy_xlate(struct device *dev,
+				    struct of_phandle_args *args)
+{
+	struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
+	struct cygnus_phy_instance *instance_ptr;
+
+	if (!phy_driver)
+		return ERR_PTR(-EINVAL);
+
+	if (WARN_ON(args->args[0] >= phy_driver->num_phys))
+		return ERR_PTR(-ENODEV);
+
+	if (WARN_ON(args->args_count < 1))
+		return ERR_PTR(-EINVAL);
+
+	instance_ptr = &phy_driver->instances[args->args[0]];
+	if (instance_ptr->port > phy_driver->phyto_src_clk)
+		phy_driver->phyto_src_clk = instance_ptr->port;
+
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		goto ret_p2;
+	phy_driver->instances[instance_ptr->port].new_state =
+						PHY2_DEV_HOST_CTRL_SEL_HOST;
+
+ret_p2:
+	return phy_driver->instances[instance_ptr->port].generic_phy;
+}
+
+static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
+{
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	reg_val = readl(phy_driver->usb2d_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+	if (enable)
+		reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+	else
+		reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+
+	writel(reg_val, phy_driver->usb2d_idm_regs +
+		USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+	reg_val = readl(phy_driver->usb2d_idm_regs +
+			USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+
+	if (enable)
+		reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
+	else
+		reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
+
+	writel(reg_val, phy_driver->usb2d_idm_regs +
+		USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+}
+
+static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
+					    u32 src_phy)
+{
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	writel(src_phy, phy_driver->cdru_usbphy_regs +
+		CDRU_USBPHY_CLK_RST_SEL_OFFSET);
+
+	/* Force the clock/reset source phy to not suspend */
+	reg_val = readl(phy_driver->usb2h_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+	reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
+			SUSPEND_OVERRIDE_2);
+
+	reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
+
+	writel(reg_val, phy_driver->usb2h_idm_regs +
+		USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+}
+
+static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
+{
+	u32 reg_val;
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
+		writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
+			phy_driver->cdru_usbphy_regs +
+			CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
+		return;
+	}
+
+	/*
+	 * Disable suspend/resume signals to device controller
+	 * when a port is in device mode
+	 */
+	writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
+		phy_driver->cdru_usbphy_regs +
+		CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
+	reg_val = readl(phy_driver->cdru_usbphy_regs +
+			CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
+	reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
+	writel(reg_val, phy_driver->cdru_usbphy_regs +
+			CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
+}
+
+static int cygnus_phy_init(struct phy *generic_phy)
+{
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_phy_dual_role_init(instance_ptr);
+
+	return 0;
+}
+
+static int cygnus_phy_shutdown(struct phy *generic_phy)
+{
+	u32 reg_val;
+	int i, ret;
+	unsigned long flags;
+	bool power_off_flag = true;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	if (instance_ptr->vbus_supply) {
+		ret = regulator_disable(instance_ptr->vbus_supply);
+		if (ret) {
+			dev_err(&generic_phy->dev,
+					"Failed to disable regulator\n");
+			return ret;
+		}
+	}
+
+	spin_lock_irqsave(&instance_ptr->lock, flags);
+
+	/* power down the phy */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	if (instance_ptr->port == 0) {
+		reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P0_RESETB;
+	} else if (instance_ptr->port == 1) {
+		reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P1_RESETB;
+	} else if (instance_ptr->port == 2) {
+		reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P2_RESETB;
+	}
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+
+	instance_ptr->power = false;
+
+	/* Determine whether all the phy's are powered off */
+	for (i = 0; i < phy_driver->num_phys; i++) {
+		if (phy_driver->instances[i].power == true) {
+			power_off_flag = false;
+			break;
+		}
+	}
+
+	/* Disable clock to USB device and keep the USB device in reset */
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
+					    false);
+
+	/*
+	 * Put the host controller into reset state and
+	 * disable clock if all the phy's are powered off
+	 */
+	if (power_off_flag) {
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+		reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				 USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		phy_driver->idm_host_enabled = false;
+	}
+	spin_unlock_irqrestore(&instance_ptr->lock, flags);
+	return 0;
+}
+
+static int cygnus_phy_poweron(struct phy *generic_phy)
+{
+	int ret;
+	unsigned long flags;
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+	u32 extcon_event = instance_ptr->new_state;
+
+	/*
+	 * Switch on the regulator only if in HOST mode
+	 */
+	if (instance_ptr->vbus_supply) {
+		ret = regulator_enable(instance_ptr->vbus_supply);
+		if (ret) {
+			dev_err(&generic_phy->dev,
+				"Failed to enable regulator\n");
+			goto err_shutdown;
+		}
+	}
+
+	spin_lock_irqsave(&instance_ptr->lock, flags);
+	/* Bring the AFE block out of reset to start powering up the PHY */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	if (instance_ptr->port == 0)
+		reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+	else if (instance_ptr->port == 1)
+		reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+	else if (instance_ptr->port == 2)
+		reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+
+	/* Check for power on and PLL lock */
+	ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
+				CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
+	if (ret < 0) {
+		dev_err(&generic_phy->dev,
+			"Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
+			instance_ptr->port);
+		spin_unlock_irqrestore(&instance_ptr->lock, flags);
+		goto err_shutdown;
+	}
+	ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
+				CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
+	if (ret < 0) {
+		dev_err(&generic_phy->dev,
+			"Timed out waiting for USBPHY_PLL_LOCK on port %d",
+			instance_ptr->port);
+		spin_unlock_irqrestore(&instance_ptr->lock, flags);
+		goto err_shutdown;
+	}
+
+	instance_ptr->power = true;
+
+	/* Enable clock to USB device and take the USB device out of reset */
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
+
+	/* Set clock source provider to be the last powered on phy */
+	if (instance_ptr->port == phy_driver->phyto_src_clk)
+		cygnus_phy_clk_reset_src_switch(generic_phy,
+				instance_ptr->port);
+
+	if (phy_driver->idm_host_enabled != true &&
+		extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
+		/* Enable clock to USB host and take the host out of reset */
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+		reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				 USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		phy_driver->idm_host_enabled = true;
+	}
+	spin_unlock_irqrestore(&instance_ptr->lock, flags);
+
+	return 0;
+err_shutdown:
+	cygnus_phy_shutdown(generic_phy);
+	return ret;
+}
+
+static int usbd_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct cygnus_phy_instance *instance_ptr = container_of(self,
+					struct cygnus_phy_instance, device_nb);
+
+	if (event) {
+		instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
+		cygnus_phy_dual_role_init(instance_ptr);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int usbh_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct cygnus_phy_instance *instance_ptr = container_of(self,
+					struct cygnus_phy_instance, host_nb);
+	if (event) {
+		instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
+		cygnus_phy_dual_role_init(instance_ptr);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int cygnus_register_extcon_notifiers(
+				struct cygnus_phy_instance *instance_ptr)
+{
+	int ret = 0;
+	struct device *dev = &instance_ptr->generic_phy->dev;
+
+	if (of_property_read_bool(dev->of_node, "extcon")) {
+		instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
+		if (IS_ERR(instance_ptr->edev)) {
+			if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
+				return -EPROBE_DEFER;
+			ret = PTR_ERR(instance_ptr->edev);
+			goto err;
+		}
+
+		instance_ptr->device_nb.notifier_call = usbd_connect_notify;
+		ret = devm_extcon_register_notifier(dev,
+						instance_ptr->edev,
+						EXTCON_USB,
+						&instance_ptr->device_nb);
+		if (ret < 0) {
+			dev_err(dev, "Can't register extcon notifier\n");
+			goto err;
+		}
+
+		if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
+			instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
+			cygnus_phy_dual_role_init(instance_ptr);
+		}
+
+		instance_ptr->host_nb.notifier_call = usbh_connect_notify;
+		ret = devm_extcon_register_notifier(dev,
+						instance_ptr->edev,
+						EXTCON_USB_HOST,
+						&instance_ptr->host_nb);
+		if (ret < 0) {
+			dev_err(dev, "Can't register extcon notifier\n");
+			goto err;
+		}
+
+		if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
+					== true) {
+			instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
+			cygnus_phy_dual_role_init(instance_ptr);
+		}
+	} else {
+		dev_err(dev, "Extcon device handle not found\n");
+		return -EINVAL;
+	}
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct phy_ops ops = {
+	.init		= cygnus_phy_init,
+	.power_on	= cygnus_phy_poweron,
+	.power_off	= cygnus_phy_shutdown,
+	.owner = THIS_MODULE,
+};
+
+static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
+{
+	u32 reg_val;
+
+	/*
+	 * Shutdown all ports. They can be powered up as
+	 * required
+	 */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P0_RESETB;
+	reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P1_RESETB;
+	reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P2_RESETB;
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+}
+
+static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
+{
+	struct device_node *child;
+	char *vbus_name;
+	struct platform_device *pdev = phy_driver->pdev;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct cygnus_phy_instance *instance_ptr;
+	unsigned int id, ret;
+
+	for_each_available_child_of_node(node, child) {
+		if (of_property_read_u32(child, "reg", &id)) {
+			dev_err(dev, "missing reg property for %s\n",
+				child->name);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		if (id >= MAX_NUM_PHYS) {
+			dev_err(dev, "invalid PHY id: %u\n", id);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		instance_ptr = &phy_driver->instances[id];
+		instance_ptr->driver = phy_driver;
+		instance_ptr->port = id;
+		spin_lock_init(&instance_ptr->lock);
+
+		if (instance_ptr->generic_phy) {
+			dev_err(dev, "duplicated PHY id: %u\n", id);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		instance_ptr->generic_phy =
+				devm_phy_create(dev, child, &ops);
+		if (IS_ERR(instance_ptr->generic_phy)) {
+			dev_err(dev, "Failed to create usb phy %d", id);
+			ret = PTR_ERR(instance_ptr->generic_phy);
+			goto put_child;
+		}
+
+		vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
+					MAX_REGULATOR_NAME_LEN,
+					GFP_KERNEL);
+		if (!vbus_name) {
+			ret = -ENOMEM;
+			goto put_child;
+		}
+
+		/* regulator use is optional */
+		sprintf(vbus_name, "vbus-p%d", id);
+		instance_ptr->vbus_supply =
+			devm_regulator_get(&instance_ptr->generic_phy->dev,
+					vbus_name);
+		if (IS_ERR(instance_ptr->vbus_supply))
+			instance_ptr->vbus_supply = NULL;
+		devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
+		phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
+	}
+
+	return 0;
+
+put_child:
+	of_node_put(child);
+	return ret;
+}
+
+static int cygnus_phy_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct cygnus_phy_driver *phy_driver;
+	struct phy_provider *phy_provider;
+	int i, ret;
+	u32 reg_val;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+
+	/* allocate memory for each phy instance */
+	phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
+				  GFP_KERNEL);
+	if (!phy_driver)
+		return -ENOMEM;
+
+	phy_driver->num_phys = of_get_child_count(node);
+
+	if (phy_driver->num_phys == 0) {
+		dev_err(dev, "PHY no child node\n");
+		return -ENODEV;
+	}
+
+	phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
+					     sizeof(struct cygnus_phy_instance),
+					     GFP_KERNEL);
+	if (!phy_driver->instances)
+		return -ENOMEM;
+
+	phy_driver->pdev = pdev;
+	platform_set_drvdata(pdev, phy_driver);
+
+	ret = cygnus_phy_instance_create(phy_driver);
+	if (ret)
+		return ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "crmu-usbphy-aon-ctrl");
+	phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
+		return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "cdru-usbphy");
+	phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->cdru_usbphy_regs))
+		return PTR_ERR(phy_driver->cdru_usbphy_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
+	phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->usb2h_idm_regs))
+		return PTR_ERR(phy_driver->usb2h_idm_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
+	phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->usb2d_idm_regs))
+		return PTR_ERR(phy_driver->usb2d_idm_regs);
+
+	reg_val = readl(phy_driver->usb2d_idm_regs);
+	writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
+		   phy_driver->usb2d_idm_regs);
+	phy_driver->idm_host_enabled = false;
+
+	/* Shutdown all ports. They can be powered up as required */
+	cygnus_phy_shutdown_all(phy_driver);
+
+	phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
+	if (IS_ERR(phy_provider)) {
+		dev_err(dev, "Failed to register as phy provider\n");
+		ret = PTR_ERR(phy_provider);
+		return ret;
+	}
+
+	for (i = 0; i < phy_driver->num_phys; i++) {
+		if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
+			ret = cygnus_register_extcon_notifiers(
+				&phy_driver->instances[DUAL_ROLE_PHY]);
+			if (ret) {
+				dev_err(dev, "Failed to register notifier\n");
+				return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id cygnus_phy_dt_ids[] = {
+	{ .compatible = "brcm,cygnus-usb-phy", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
+
+static struct platform_driver cygnus_phy_driver = {
+	.probe = cygnus_phy_probe,
+	.driver = {
+		.name = "bcm-cygnus-usbphy",
+		.of_match_table = of_match_ptr(cygnus_phy_dt_ids),
+	},
+};
+module_platform_driver(cygnus_phy_driver);
+
+MODULE_ALIAS("platform:bcm-cygnus-usbphy");
+MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org");
+MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: linux-arm-kernel

Add driver for Broadcom's USB phy controller's used in Cygnus
familyof SoC. Cygnus has three USB phy controller's, port 0,
port 1 provides USB host functionality and port 2 can be configured
for host/device role.

Configuration of host/device role for port 2 is achieved based on
the extcon events, the driver registers to extcon framework to get
appropriate connect events for Host/Device cables connect/disconnect
states based on VBUS and ID interrupts.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
---
 drivers/phy/broadcom/Kconfig              |  14 +
 drivers/phy/broadcom/Makefile             |   1 +
 drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
 3 files changed, 705 insertions(+)
 create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c

diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
index 64fc59c..3179daf 100644
--- a/drivers/phy/broadcom/Kconfig
+++ b/drivers/phy/broadcom/Kconfig
@@ -1,6 +1,20 @@
 #
 # Phy drivers for Broadcom platforms
 #
+config PHY_BCM_CYGNUS_USB
+	tristate "Broadcom Cygnus USB PHY support"
+	depends on OF
+	depends on ARCH_BCM_CYGNUS || COMPILE_TEST
+	select GENERIC_PHY
+	select EXTCON_USB_GPIO
+	default ARCH_BCM_CYGNUS
+	help
+	  Enable this to support three USB PHY's present in Broadcom's
+	  Cygnus chip.
+
+	  The phys are capable of supporting host mode on all ports and
+	  device mode for port 2.
+
 config PHY_CYGNUS_PCIE
 	tristate "Broadcom Cygnus PCIe PHY driver"
 	depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
index 4eb82ec..3dec23c 100644
--- a/drivers/phy/broadcom/Makefile
+++ b/drivers/phy/broadcom/Makefile
@@ -1,4 +1,5 @@
 obj-$(CONFIG_PHY_CYGNUS_PCIE)		+= phy-bcm-cygnus-pcie.o
+obj-$(CONFIG_PHY_BCM_CYGNUS_USB)	+= phy-bcm-cygnus-usb.o
 obj-$(CONFIG_BCM_KONA_USB2_PHY)		+= phy-bcm-kona-usb2.o
 obj-$(CONFIG_PHY_BCM_NS_USB2)		+= phy-bcm-ns-usb2.o
 obj-$(CONFIG_PHY_BCM_NS_USB3)		+= phy-bcm-ns-usb3.o
diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
new file mode 100644
index 0000000..cf957d4
--- /dev/null
+++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
@@ -0,0 +1,690 @@
+/*
+ * Copyright 2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation (the "GPL").
+ *
+ * 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 version 2 (GPLv2) for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 (GPLv2) along with this source code.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/extcon.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+
+/* CDRU Block Register Offsets and bit definitions */
+#define CDRU_USBPHY_CLK_RST_SEL_OFFSET			0x0
+#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET		0x4
+#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET		0x5C
+#define CDRU_USBPHY_P0_STATUS_OFFSET			0x1C
+#define CDRU_USBPHY_P1_STATUS_OFFSET			0x34
+#define CDRU_USBPHY_P2_STATUS_OFFSET			0x4C
+
+#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG			BIT(1)
+#define CDRU_USBPHY_USBPHY_PLL_LOCK			BIT(0)
+#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE	BIT(0)
+
+/* CRMU Block Register Offsets and bit definitions */
+#define CRMU_USB_PHY_AON_CTRL_OFFSET			0x0
+#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC			BIT(1)
+#define CRMU_USBPHY_P0_RESETB				BIT(2)
+#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC			BIT(9)
+#define CRMU_USBPHY_P1_RESETB				BIT(10)
+#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC			BIT(17)
+#define CRMU_USBPHY_P2_RESETB				BIT(18)
+
+/* USB2 IDM Block register Offset and bit definitions */
+#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET		0x0408
+#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE	BIT(0)
+#define SUSPEND_OVERRIDE_0				BIT(13)
+#define SUSPEND_OVERRIDE_0_POS				13
+#define SUSPEND_OVERRIDE_1				BIT(14)
+#define SUSPEND_OVERRIDE_1_POS				14
+#define SUSPEND_OVERRIDE_2				BIT(15)
+#define SUSPEND_OVERRIDE_2_POS				15
+
+#define USB2_IDM_IDM_RESET_CONTROL_OFFSET		0x0800
+#define USB2_IDM_IDM_RESET_CONTROL__RESET		BIT(0)
+#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I	BIT(24)
+
+#define PHY2_DEV_HOST_CTRL_SEL_DEVICE			0
+#define PHY2_DEV_HOST_CTRL_SEL_HOST			1
+#define PHY2_DEV_HOST_CTRL_SEL_IDLE			2
+
+#define PLL_LOCK_RETRY_COUNT				1000
+#define MAX_REGULATOR_NAME_LEN				25
+#define DUAL_ROLE_PHY					2
+
+#define USBPHY_WQ_DELAY_MS		msecs_to_jiffies(500)
+#define USB2_SEL_DEVICE			0
+#define USB2_SEL_HOST			1
+#define USB2_SEL_IDLE			2
+#define USB_CONNECTED			1
+#define USB_DISCONNECTED		0
+#define MAX_NUM_PHYS			3
+
+static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
+				CDRU_USBPHY_P1_STATUS_OFFSET,
+				CDRU_USBPHY_P2_STATUS_OFFSET};
+
+struct cygnus_phy_instance;
+
+struct cygnus_phy_driver {
+	void __iomem *cdru_usbphy_regs;
+	void __iomem *crmu_usbphy_aon_ctrl_regs;
+	void __iomem *usb2h_idm_regs;
+	void __iomem *usb2d_idm_regs;
+	int num_phys;
+	bool idm_host_enabled;
+	struct cygnus_phy_instance *instances;
+	int phyto_src_clk;
+	struct platform_device *pdev;
+};
+
+struct cygnus_phy_instance {
+	struct cygnus_phy_driver *driver;
+	struct phy *generic_phy;
+	int port;
+	int new_state;		/* 1 - Host, 0 - device, 2 - idle*/
+	bool power;		/* 1 - powered_on 0 - powered off */
+	struct regulator *vbus_supply;
+	spinlock_t lock;
+	struct extcon_dev *edev;
+	struct notifier_block	device_nb;
+	struct notifier_block	host_nb;
+};
+
+static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
+				    struct cygnus_phy_driver *phy_driver)
+{
+	/* Wait for the PLL lock status */
+	int retry = PLL_LOCK_RETRY_COUNT;
+	u32 reg_val;
+
+	do {
+		udelay(1);
+		reg_val = readl(phy_driver->cdru_usbphy_regs +
+				usb_reg);
+		if (reg_val & bit_mask)
+			return 0;
+	} while (--retry > 0);
+
+	return -EBUSY;
+}
+
+static struct phy *cygnus_phy_xlate(struct device *dev,
+				    struct of_phandle_args *args)
+{
+	struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
+	struct cygnus_phy_instance *instance_ptr;
+
+	if (!phy_driver)
+		return ERR_PTR(-EINVAL);
+
+	if (WARN_ON(args->args[0] >= phy_driver->num_phys))
+		return ERR_PTR(-ENODEV);
+
+	if (WARN_ON(args->args_count < 1))
+		return ERR_PTR(-EINVAL);
+
+	instance_ptr = &phy_driver->instances[args->args[0]];
+	if (instance_ptr->port > phy_driver->phyto_src_clk)
+		phy_driver->phyto_src_clk = instance_ptr->port;
+
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		goto ret_p2;
+	phy_driver->instances[instance_ptr->port].new_state =
+						PHY2_DEV_HOST_CTRL_SEL_HOST;
+
+ret_p2:
+	return phy_driver->instances[instance_ptr->port].generic_phy;
+}
+
+static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
+{
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	reg_val = readl(phy_driver->usb2d_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+	if (enable)
+		reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+	else
+		reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+
+	writel(reg_val, phy_driver->usb2d_idm_regs +
+		USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+	reg_val = readl(phy_driver->usb2d_idm_regs +
+			USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+
+	if (enable)
+		reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
+	else
+		reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
+
+	writel(reg_val, phy_driver->usb2d_idm_regs +
+		USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+}
+
+static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
+					    u32 src_phy)
+{
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	writel(src_phy, phy_driver->cdru_usbphy_regs +
+		CDRU_USBPHY_CLK_RST_SEL_OFFSET);
+
+	/* Force the clock/reset source phy to not suspend */
+	reg_val = readl(phy_driver->usb2h_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+	reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
+			SUSPEND_OVERRIDE_2);
+
+	reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
+
+	writel(reg_val, phy_driver->usb2h_idm_regs +
+		USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+}
+
+static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
+{
+	u32 reg_val;
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
+		writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
+			phy_driver->cdru_usbphy_regs +
+			CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
+		return;
+	}
+
+	/*
+	 * Disable suspend/resume signals to device controller
+	 * when a port is in device mode
+	 */
+	writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
+		phy_driver->cdru_usbphy_regs +
+		CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
+	reg_val = readl(phy_driver->cdru_usbphy_regs +
+			CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
+	reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
+	writel(reg_val, phy_driver->cdru_usbphy_regs +
+			CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
+}
+
+static int cygnus_phy_init(struct phy *generic_phy)
+{
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_phy_dual_role_init(instance_ptr);
+
+	return 0;
+}
+
+static int cygnus_phy_shutdown(struct phy *generic_phy)
+{
+	u32 reg_val;
+	int i, ret;
+	unsigned long flags;
+	bool power_off_flag = true;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+
+	if (instance_ptr->vbus_supply) {
+		ret = regulator_disable(instance_ptr->vbus_supply);
+		if (ret) {
+			dev_err(&generic_phy->dev,
+					"Failed to disable regulator\n");
+			return ret;
+		}
+	}
+
+	spin_lock_irqsave(&instance_ptr->lock, flags);
+
+	/* power down the phy */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	if (instance_ptr->port == 0) {
+		reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P0_RESETB;
+	} else if (instance_ptr->port == 1) {
+		reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P1_RESETB;
+	} else if (instance_ptr->port == 2) {
+		reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+		reg_val &= ~CRMU_USBPHY_P2_RESETB;
+	}
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+
+	instance_ptr->power = false;
+
+	/* Determine whether all the phy's are powered off */
+	for (i = 0; i < phy_driver->num_phys; i++) {
+		if (phy_driver->instances[i].power == true) {
+			power_off_flag = false;
+			break;
+		}
+	}
+
+	/* Disable clock to USB device and keep the USB device in reset */
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
+					    false);
+
+	/*
+	 * Put the host controller into reset state and
+	 * disable clock if all the phy's are powered off
+	 */
+	if (power_off_flag) {
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+			USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+		reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				 USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		phy_driver->idm_host_enabled = false;
+	}
+	spin_unlock_irqrestore(&instance_ptr->lock, flags);
+	return 0;
+}
+
+static int cygnus_phy_poweron(struct phy *generic_phy)
+{
+	int ret;
+	unsigned long flags;
+	u32 reg_val;
+	struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
+	struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
+	u32 extcon_event = instance_ptr->new_state;
+
+	/*
+	 * Switch on the regulator only if in HOST mode
+	 */
+	if (instance_ptr->vbus_supply) {
+		ret = regulator_enable(instance_ptr->vbus_supply);
+		if (ret) {
+			dev_err(&generic_phy->dev,
+				"Failed to enable regulator\n");
+			goto err_shutdown;
+		}
+	}
+
+	spin_lock_irqsave(&instance_ptr->lock, flags);
+	/* Bring the AFE block out of reset to start powering up the PHY */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	if (instance_ptr->port == 0)
+		reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+	else if (instance_ptr->port == 1)
+		reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+	else if (instance_ptr->port == 2)
+		reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+
+	/* Check for power on and PLL lock */
+	ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
+				CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
+	if (ret < 0) {
+		dev_err(&generic_phy->dev,
+			"Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
+			instance_ptr->port);
+		spin_unlock_irqrestore(&instance_ptr->lock, flags);
+		goto err_shutdown;
+	}
+	ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
+				CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
+	if (ret < 0) {
+		dev_err(&generic_phy->dev,
+			"Timed out waiting for USBPHY_PLL_LOCK on port %d",
+			instance_ptr->port);
+		spin_unlock_irqrestore(&instance_ptr->lock, flags);
+		goto err_shutdown;
+	}
+
+	instance_ptr->power = true;
+
+	/* Enable clock to USB device and take the USB device out of reset */
+	if (instance_ptr->port == DUAL_ROLE_PHY)
+		cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
+
+	/* Set clock source provider to be the last powered on phy */
+	if (instance_ptr->port == phy_driver->phyto_src_clk)
+		cygnus_phy_clk_reset_src_switch(generic_phy,
+				instance_ptr->port);
+
+	if (phy_driver->idm_host_enabled != true &&
+		extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
+		/* Enable clock to USB host and take the host out of reset */
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+		reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
+
+		reg_val = readl(phy_driver->usb2h_idm_regs +
+				USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
+		writel(reg_val, phy_driver->usb2h_idm_regs +
+				 USB2_IDM_IDM_RESET_CONTROL_OFFSET);
+		phy_driver->idm_host_enabled = true;
+	}
+	spin_unlock_irqrestore(&instance_ptr->lock, flags);
+
+	return 0;
+err_shutdown:
+	cygnus_phy_shutdown(generic_phy);
+	return ret;
+}
+
+static int usbd_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct cygnus_phy_instance *instance_ptr = container_of(self,
+					struct cygnus_phy_instance, device_nb);
+
+	if (event) {
+		instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
+		cygnus_phy_dual_role_init(instance_ptr);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int usbh_connect_notify(struct notifier_block *self,
+			       unsigned long event, void *ptr)
+{
+	struct cygnus_phy_instance *instance_ptr = container_of(self,
+					struct cygnus_phy_instance, host_nb);
+	if (event) {
+		instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
+		cygnus_phy_dual_role_init(instance_ptr);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int cygnus_register_extcon_notifiers(
+				struct cygnus_phy_instance *instance_ptr)
+{
+	int ret = 0;
+	struct device *dev = &instance_ptr->generic_phy->dev;
+
+	if (of_property_read_bool(dev->of_node, "extcon")) {
+		instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
+		if (IS_ERR(instance_ptr->edev)) {
+			if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
+				return -EPROBE_DEFER;
+			ret = PTR_ERR(instance_ptr->edev);
+			goto err;
+		}
+
+		instance_ptr->device_nb.notifier_call = usbd_connect_notify;
+		ret = devm_extcon_register_notifier(dev,
+						instance_ptr->edev,
+						EXTCON_USB,
+						&instance_ptr->device_nb);
+		if (ret < 0) {
+			dev_err(dev, "Can't register extcon notifier\n");
+			goto err;
+		}
+
+		if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
+			instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
+			cygnus_phy_dual_role_init(instance_ptr);
+		}
+
+		instance_ptr->host_nb.notifier_call = usbh_connect_notify;
+		ret = devm_extcon_register_notifier(dev,
+						instance_ptr->edev,
+						EXTCON_USB_HOST,
+						&instance_ptr->host_nb);
+		if (ret < 0) {
+			dev_err(dev, "Can't register extcon notifier\n");
+			goto err;
+		}
+
+		if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
+					== true) {
+			instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
+			cygnus_phy_dual_role_init(instance_ptr);
+		}
+	} else {
+		dev_err(dev, "Extcon device handle not found\n");
+		return -EINVAL;
+	}
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct phy_ops ops = {
+	.init		= cygnus_phy_init,
+	.power_on	= cygnus_phy_poweron,
+	.power_off	= cygnus_phy_shutdown,
+	.owner = THIS_MODULE,
+};
+
+static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
+{
+	u32 reg_val;
+
+	/*
+	 * Shutdown all ports. They can be powered up as
+	 * required
+	 */
+	reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
+			CRMU_USB_PHY_AON_CTRL_OFFSET);
+	reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P0_RESETB;
+	reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P1_RESETB;
+	reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
+	reg_val &= ~CRMU_USBPHY_P2_RESETB;
+	writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
+		CRMU_USB_PHY_AON_CTRL_OFFSET);
+}
+
+static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
+{
+	struct device_node *child;
+	char *vbus_name;
+	struct platform_device *pdev = phy_driver->pdev;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct cygnus_phy_instance *instance_ptr;
+	unsigned int id, ret;
+
+	for_each_available_child_of_node(node, child) {
+		if (of_property_read_u32(child, "reg", &id)) {
+			dev_err(dev, "missing reg property for %s\n",
+				child->name);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		if (id >= MAX_NUM_PHYS) {
+			dev_err(dev, "invalid PHY id: %u\n", id);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		instance_ptr = &phy_driver->instances[id];
+		instance_ptr->driver = phy_driver;
+		instance_ptr->port = id;
+		spin_lock_init(&instance_ptr->lock);
+
+		if (instance_ptr->generic_phy) {
+			dev_err(dev, "duplicated PHY id: %u\n", id);
+			ret = -EINVAL;
+			goto put_child;
+		}
+
+		instance_ptr->generic_phy =
+				devm_phy_create(dev, child, &ops);
+		if (IS_ERR(instance_ptr->generic_phy)) {
+			dev_err(dev, "Failed to create usb phy %d", id);
+			ret = PTR_ERR(instance_ptr->generic_phy);
+			goto put_child;
+		}
+
+		vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
+					MAX_REGULATOR_NAME_LEN,
+					GFP_KERNEL);
+		if (!vbus_name) {
+			ret = -ENOMEM;
+			goto put_child;
+		}
+
+		/* regulator use is optional */
+		sprintf(vbus_name, "vbus-p%d", id);
+		instance_ptr->vbus_supply =
+			devm_regulator_get(&instance_ptr->generic_phy->dev,
+					vbus_name);
+		if (IS_ERR(instance_ptr->vbus_supply))
+			instance_ptr->vbus_supply = NULL;
+		devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
+		phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
+	}
+
+	return 0;
+
+put_child:
+	of_node_put(child);
+	return ret;
+}
+
+static int cygnus_phy_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct cygnus_phy_driver *phy_driver;
+	struct phy_provider *phy_provider;
+	int i, ret;
+	u32 reg_val;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+
+	/* allocate memory for each phy instance */
+	phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
+				  GFP_KERNEL);
+	if (!phy_driver)
+		return -ENOMEM;
+
+	phy_driver->num_phys = of_get_child_count(node);
+
+	if (phy_driver->num_phys == 0) {
+		dev_err(dev, "PHY no child node\n");
+		return -ENODEV;
+	}
+
+	phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
+					     sizeof(struct cygnus_phy_instance),
+					     GFP_KERNEL);
+	if (!phy_driver->instances)
+		return -ENOMEM;
+
+	phy_driver->pdev = pdev;
+	platform_set_drvdata(pdev, phy_driver);
+
+	ret = cygnus_phy_instance_create(phy_driver);
+	if (ret)
+		return ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "crmu-usbphy-aon-ctrl");
+	phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
+		return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "cdru-usbphy");
+	phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->cdru_usbphy_regs))
+		return PTR_ERR(phy_driver->cdru_usbphy_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
+	phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->usb2h_idm_regs))
+		return PTR_ERR(phy_driver->usb2h_idm_regs);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
+	phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(phy_driver->usb2d_idm_regs))
+		return PTR_ERR(phy_driver->usb2d_idm_regs);
+
+	reg_val = readl(phy_driver->usb2d_idm_regs);
+	writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
+		   phy_driver->usb2d_idm_regs);
+	phy_driver->idm_host_enabled = false;
+
+	/* Shutdown all ports. They can be powered up as required */
+	cygnus_phy_shutdown_all(phy_driver);
+
+	phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
+	if (IS_ERR(phy_provider)) {
+		dev_err(dev, "Failed to register as phy provider\n");
+		ret = PTR_ERR(phy_provider);
+		return ret;
+	}
+
+	for (i = 0; i < phy_driver->num_phys; i++) {
+		if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
+			ret = cygnus_register_extcon_notifiers(
+				&phy_driver->instances[DUAL_ROLE_PHY]);
+			if (ret) {
+				dev_err(dev, "Failed to register notifier\n");
+				return ret;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id cygnus_phy_dt_ids[] = {
+	{ .compatible = "brcm,cygnus-usb-phy", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
+
+static struct platform_driver cygnus_phy_driver = {
+	.probe = cygnus_phy_probe,
+	.driver = {
+		.name = "bcm-cygnus-usbphy",
+		.of_match_table = of_match_ptr(cygnus_phy_dt_ids),
+	},
+};
+module_platform_driver(cygnus_phy_driver);
+
+MODULE_ALIAS("platform:bcm-cygnus-usbphy");
+MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
+MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
+MODULE_LICENSE("GPL v2");
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 3/3] ARM: dts: Add dt node for Broadcom Cygnus USB phy
  2017-11-08  7:46 ` Raveendra Padasalagi
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  -1 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung
  Cc: devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list, Raveendra Padasalagi

Add DT node for Broadcom's USB phy controller's used
in Cygnus family of SoC.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
---
 arch/arm/boot/dts/bcm-cygnus.dtsi | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 7c957ea..c7b39dd 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -96,6 +96,41 @@
 		#address-cells = <1>;
 		#size-cells = <1>;
 
+		extcon_usb: extcon_usb {
+			compatible = "linux,extcon-usb-gpio";
+			vbus-gpio = <&gpio_asiu 121 0>;
+			id-gpio = <&gpio_asiu 122 0>;
+			status = "okay";
+		};
+
+		usbphy: usb-phy@0301c028 {
+			compatible = "brcm,cygnus-usb-phy";
+			reg = <0x0301c028 0x4>,
+			      <0x0301d1b4 0x5c>,
+			      <0x18115000 0xa00>,
+			      <0x18111000 0xa00>;
+			reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
+				    "usb2h-idm", "usb2d-idm";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			usbphy0: usb-phy@0 {
+				#phy-cells = <1>;
+				reg = <0>;
+			};
+
+			usbphy1: usb-phy@1 {
+				#phy-cells = <1>;
+				reg = <1>;
+			};
+
+			usbphy2: usb-phy@2 {
+				#phy-cells = <1>;
+				reg = <2>;
+				extcon = <&extcon_usb>;
+			};
+		};
+
 		otp: otp@0301c800 {
 			compatible = "brcm,ocotp";
 			reg = <0x0301c800 0x2c>;
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* [PATCH v2 3/3] ARM: dts: Add dt node for Broadcom Cygnus USB phy
@ 2017-11-08  7:46   ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:46 UTC (permalink / raw)
  To: linux-arm-kernel

Add DT node for Broadcom's USB phy controller's used
in Cygnus family of SoC.

Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
---
 arch/arm/boot/dts/bcm-cygnus.dtsi | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/arch/arm/boot/dts/bcm-cygnus.dtsi b/arch/arm/boot/dts/bcm-cygnus.dtsi
index 7c957ea..c7b39dd 100644
--- a/arch/arm/boot/dts/bcm-cygnus.dtsi
+++ b/arch/arm/boot/dts/bcm-cygnus.dtsi
@@ -96,6 +96,41 @@
 		#address-cells = <1>;
 		#size-cells = <1>;
 
+		extcon_usb: extcon_usb {
+			compatible = "linux,extcon-usb-gpio";
+			vbus-gpio = <&gpio_asiu 121 0>;
+			id-gpio = <&gpio_asiu 122 0>;
+			status = "okay";
+		};
+
+		usbphy: usb-phy at 0301c028 {
+			compatible = "brcm,cygnus-usb-phy";
+			reg = <0x0301c028 0x4>,
+			      <0x0301d1b4 0x5c>,
+			      <0x18115000 0xa00>,
+			      <0x18111000 0xa00>;
+			reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
+				    "usb2h-idm", "usb2d-idm";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			usbphy0: usb-phy at 0 {
+				#phy-cells = <1>;
+				reg = <0>;
+			};
+
+			usbphy1: usb-phy at 1 {
+				#phy-cells = <1>;
+				reg = <1>;
+			};
+
+			usbphy2: usb-phy at 2 {
+				#phy-cells = <1>;
+				reg = <2>;
+				extcon = <&extcon_usb>;
+			};
+		};
+
 		otp: otp at 0301c800 {
 			compatible = "brcm,ocotp";
 			reg = <0x0301c800 0x2c>;
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08  7:52     ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:52 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, Chanwoo Choi
  Cc: devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list, Raveendra Padasalagi

Hi,

Adding Chanwoo Choi to review extcon API's.

-Raveendra
On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
<raveendra.padasalagi@broadcom.com> wrote:
> Add driver for Broadcom's USB phy controller's used in Cygnus
> familyof SoC. Cygnus has three USB phy controller's, port 0,
> port 1 provides USB host functionality and port 2 can be configured
> for host/device role.
>
> Configuration of host/device role for port 2 is achieved based on
> the extcon events, the driver registers to extcon framework to get
> appropriate connect events for Host/Device cables connect/disconnect
> states based on VBUS and ID interrupts.
>
> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
> ---
>  drivers/phy/broadcom/Kconfig              |  14 +
>  drivers/phy/broadcom/Makefile             |   1 +
>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>  3 files changed, 705 insertions(+)
>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>
> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
> index 64fc59c..3179daf 100644
> --- a/drivers/phy/broadcom/Kconfig
> +++ b/drivers/phy/broadcom/Kconfig
> @@ -1,6 +1,20 @@
>  #
>  # Phy drivers for Broadcom platforms
>  #
> +config PHY_BCM_CYGNUS_USB
> +       tristate "Broadcom Cygnus USB PHY support"
> +       depends on OF
> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
> +       select GENERIC_PHY
> +       select EXTCON_USB_GPIO
> +       default ARCH_BCM_CYGNUS
> +       help
> +         Enable this to support three USB PHY's present in Broadcom's
> +         Cygnus chip.
> +
> +         The phys are capable of supporting host mode on all ports and
> +         device mode for port 2.
> +
>  config PHY_CYGNUS_PCIE
>         tristate "Broadcom Cygnus PCIe PHY driver"
>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
> index 4eb82ec..3dec23c 100644
> --- a/drivers/phy/broadcom/Makefile
> +++ b/drivers/phy/broadcom/Makefile
> @@ -1,4 +1,5 @@
>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
> new file mode 100644
> index 0000000..cf957d4
> --- /dev/null
> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
> @@ -0,0 +1,690 @@
> +/*
> + * Copyright 2017 Broadcom
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, version 2, as
> + * published by the Free Software Foundation (the "GPL").
> + *
> + * 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 version 2 (GPLv2) for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * version 2 (GPLv2) along with this source code.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/phy/phy.h>
> +#include <linux/delay.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/extcon.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +
> +/* CDRU Block Register Offsets and bit definitions */
> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
> +
> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
> +
> +/* CRMU Block Register Offsets and bit definitions */
> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
> +
> +/* USB2 IDM Block register Offset and bit definitions */
> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
> +#define SUSPEND_OVERRIDE_0                             BIT(13)
> +#define SUSPEND_OVERRIDE_0_POS                         13
> +#define SUSPEND_OVERRIDE_1                             BIT(14)
> +#define SUSPEND_OVERRIDE_1_POS                         14
> +#define SUSPEND_OVERRIDE_2                             BIT(15)
> +#define SUSPEND_OVERRIDE_2_POS                         15
> +
> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
> +
> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
> +
> +#define PLL_LOCK_RETRY_COUNT                           1000
> +#define MAX_REGULATOR_NAME_LEN                         25
> +#define DUAL_ROLE_PHY                                  2
> +
> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
> +#define USB2_SEL_DEVICE                        0
> +#define USB2_SEL_HOST                  1
> +#define USB2_SEL_IDLE                  2
> +#define USB_CONNECTED                  1
> +#define USB_DISCONNECTED               0
> +#define MAX_NUM_PHYS                   3
> +
> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
> +
> +struct cygnus_phy_instance;
> +
> +struct cygnus_phy_driver {
> +       void __iomem *cdru_usbphy_regs;
> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
> +       void __iomem *usb2h_idm_regs;
> +       void __iomem *usb2d_idm_regs;
> +       int num_phys;
> +       bool idm_host_enabled;
> +       struct cygnus_phy_instance *instances;
> +       int phyto_src_clk;
> +       struct platform_device *pdev;
> +};
> +
> +struct cygnus_phy_instance {
> +       struct cygnus_phy_driver *driver;
> +       struct phy *generic_phy;
> +       int port;
> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
> +       bool power;             /* 1 - powered_on 0 - powered off */
> +       struct regulator *vbus_supply;
> +       spinlock_t lock;
> +       struct extcon_dev *edev;
> +       struct notifier_block   device_nb;
> +       struct notifier_block   host_nb;
> +};
> +
> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
> +                                   struct cygnus_phy_driver *phy_driver)
> +{
> +       /* Wait for the PLL lock status */
> +       int retry = PLL_LOCK_RETRY_COUNT;
> +       u32 reg_val;
> +
> +       do {
> +               udelay(1);
> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
> +                               usb_reg);
> +               if (reg_val & bit_mask)
> +                       return 0;
> +       } while (--retry > 0);
> +
> +       return -EBUSY;
> +}
> +
> +static struct phy *cygnus_phy_xlate(struct device *dev,
> +                                   struct of_phandle_args *args)
> +{
> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
> +       struct cygnus_phy_instance *instance_ptr;
> +
> +       if (!phy_driver)
> +               return ERR_PTR(-EINVAL);
> +
> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
> +               return ERR_PTR(-ENODEV);
> +
> +       if (WARN_ON(args->args_count < 1))
> +               return ERR_PTR(-EINVAL);
> +
> +       instance_ptr = &phy_driver->instances[args->args[0]];
> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
> +               phy_driver->phyto_src_clk = instance_ptr->port;
> +
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               goto ret_p2;
> +       phy_driver->instances[instance_ptr->port].new_state =
> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
> +
> +ret_p2:
> +       return phy_driver->instances[instance_ptr->port].generic_phy;
> +}
> +
> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +       if (enable)
> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +       else
> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +
> +       writel(reg_val, phy_driver->usb2d_idm_regs +
> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs +
> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +
> +       if (enable)
> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
> +       else
> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
> +
> +       writel(reg_val, phy_driver->usb2d_idm_regs +
> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +}
> +
> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
> +                                           u32 src_phy)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
> +
> +       /* Force the clock/reset source phy to not suspend */
> +       reg_val = readl(phy_driver->usb2h_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
> +                       SUSPEND_OVERRIDE_2);
> +
> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
> +
> +       writel(reg_val, phy_driver->usb2h_idm_regs +
> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +}
> +
> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
> +                       phy_driver->cdru_usbphy_regs +
> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
> +               return;
> +       }
> +
> +       /*
> +        * Disable suspend/resume signals to device controller
> +        * when a port is in device mode
> +        */
> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
> +               phy_driver->cdru_usbphy_regs +
> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
> +}
> +
> +static int cygnus_phy_init(struct phy *generic_phy)
> +{
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_phy_dual_role_init(instance_ptr);
> +
> +       return 0;
> +}
> +
> +static int cygnus_phy_shutdown(struct phy *generic_phy)
> +{
> +       u32 reg_val;
> +       int i, ret;
> +       unsigned long flags;
> +       bool power_off_flag = true;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       if (instance_ptr->vbus_supply) {
> +               ret = regulator_disable(instance_ptr->vbus_supply);
> +               if (ret) {
> +                       dev_err(&generic_phy->dev,
> +                                       "Failed to disable regulator\n");
> +                       return ret;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&instance_ptr->lock, flags);
> +
> +       /* power down the phy */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       if (instance_ptr->port == 0) {
> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
> +       } else if (instance_ptr->port == 1) {
> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
> +       } else if (instance_ptr->port == 2) {
> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
> +       }
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +
> +       instance_ptr->power = false;
> +
> +       /* Determine whether all the phy's are powered off */
> +       for (i = 0; i < phy_driver->num_phys; i++) {
> +               if (phy_driver->instances[i].power == true) {
> +                       power_off_flag = false;
> +                       break;
> +               }
> +       }
> +
> +       /* Disable clock to USB device and keep the USB device in reset */
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
> +                                           false);
> +
> +       /*
> +        * Put the host controller into reset state and
> +        * disable clock if all the phy's are powered off
> +        */
> +       if (power_off_flag) {
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               phy_driver->idm_host_enabled = false;
> +       }
> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +       return 0;
> +}
> +
> +static int cygnus_phy_poweron(struct phy *generic_phy)
> +{
> +       int ret;
> +       unsigned long flags;
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +       u32 extcon_event = instance_ptr->new_state;
> +
> +       /*
> +        * Switch on the regulator only if in HOST mode
> +        */
> +       if (instance_ptr->vbus_supply) {
> +               ret = regulator_enable(instance_ptr->vbus_supply);
> +               if (ret) {
> +                       dev_err(&generic_phy->dev,
> +                               "Failed to enable regulator\n");
> +                       goto err_shutdown;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&instance_ptr->lock, flags);
> +       /* Bring the AFE block out of reset to start powering up the PHY */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       if (instance_ptr->port == 0)
> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +       else if (instance_ptr->port == 1)
> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +       else if (instance_ptr->port == 2)
> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +
> +       /* Check for power on and PLL lock */
> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
> +       if (ret < 0) {
> +               dev_err(&generic_phy->dev,
> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
> +                       instance_ptr->port);
> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +               goto err_shutdown;
> +       }
> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
> +       if (ret < 0) {
> +               dev_err(&generic_phy->dev,
> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
> +                       instance_ptr->port);
> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +               goto err_shutdown;
> +       }
> +
> +       instance_ptr->power = true;
> +
> +       /* Enable clock to USB device and take the USB device out of reset */
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
> +
> +       /* Set clock source provider to be the last powered on phy */
> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
> +               cygnus_phy_clk_reset_src_switch(generic_phy,
> +                               instance_ptr->port);
> +
> +       if (phy_driver->idm_host_enabled != true &&
> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
> +               /* Enable clock to USB host and take the host out of reset */
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               phy_driver->idm_host_enabled = true;
> +       }
> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +
> +       return 0;
> +err_shutdown:
> +       cygnus_phy_shutdown(generic_phy);
> +       return ret;
> +}
> +
> +static int usbd_connect_notify(struct notifier_block *self,
> +                              unsigned long event, void *ptr)
> +{
> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
> +                                       struct cygnus_phy_instance, device_nb);
> +
> +       if (event) {
> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
> +               cygnus_phy_dual_role_init(instance_ptr);
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int usbh_connect_notify(struct notifier_block *self,
> +                              unsigned long event, void *ptr)
> +{
> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
> +                                       struct cygnus_phy_instance, host_nb);
> +       if (event) {
> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
> +               cygnus_phy_dual_role_init(instance_ptr);
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int cygnus_register_extcon_notifiers(
> +                               struct cygnus_phy_instance *instance_ptr)
> +{
> +       int ret = 0;
> +       struct device *dev = &instance_ptr->generic_phy->dev;
> +
> +       if (of_property_read_bool(dev->of_node, "extcon")) {
> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
> +               if (IS_ERR(instance_ptr->edev)) {
> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
> +                               return -EPROBE_DEFER;
> +                       ret = PTR_ERR(instance_ptr->edev);
> +                       goto err;
> +               }
> +
> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
> +               ret = devm_extcon_register_notifier(dev,
> +                                               instance_ptr->edev,
> +                                               EXTCON_USB,
> +                                               &instance_ptr->device_nb);
> +               if (ret < 0) {
> +                       dev_err(dev, "Can't register extcon notifier\n");
> +                       goto err;
> +               }
> +
> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
> +                       cygnus_phy_dual_role_init(instance_ptr);
> +               }
> +
> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
> +               ret = devm_extcon_register_notifier(dev,
> +                                               instance_ptr->edev,
> +                                               EXTCON_USB_HOST,
> +                                               &instance_ptr->host_nb);
> +               if (ret < 0) {
> +                       dev_err(dev, "Can't register extcon notifier\n");
> +                       goto err;
> +               }
> +
> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
> +                                       == true) {
> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
> +                       cygnus_phy_dual_role_init(instance_ptr);
> +               }
> +       } else {
> +               dev_err(dev, "Extcon device handle not found\n");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +err:
> +       return ret;
> +}
> +
> +static const struct phy_ops ops = {
> +       .init           = cygnus_phy_init,
> +       .power_on       = cygnus_phy_poweron,
> +       .power_off      = cygnus_phy_shutdown,
> +       .owner = THIS_MODULE,
> +};
> +
> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
> +{
> +       u32 reg_val;
> +
> +       /*
> +        * Shutdown all ports. They can be powered up as
> +        * required
> +        */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +}
> +
> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
> +{
> +       struct device_node *child;
> +       char *vbus_name;
> +       struct platform_device *pdev = phy_driver->pdev;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +       struct cygnus_phy_instance *instance_ptr;
> +       unsigned int id, ret;
> +
> +       for_each_available_child_of_node(node, child) {
> +               if (of_property_read_u32(child, "reg", &id)) {
> +                       dev_err(dev, "missing reg property for %s\n",
> +                               child->name);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               if (id >= MAX_NUM_PHYS) {
> +                       dev_err(dev, "invalid PHY id: %u\n", id);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               instance_ptr = &phy_driver->instances[id];
> +               instance_ptr->driver = phy_driver;
> +               instance_ptr->port = id;
> +               spin_lock_init(&instance_ptr->lock);
> +
> +               if (instance_ptr->generic_phy) {
> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               instance_ptr->generic_phy =
> +                               devm_phy_create(dev, child, &ops);
> +               if (IS_ERR(instance_ptr->generic_phy)) {
> +                       dev_err(dev, "Failed to create usb phy %d", id);
> +                       ret = PTR_ERR(instance_ptr->generic_phy);
> +                       goto put_child;
> +               }
> +
> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
> +                                       MAX_REGULATOR_NAME_LEN,
> +                                       GFP_KERNEL);
> +               if (!vbus_name) {
> +                       ret = -ENOMEM;
> +                       goto put_child;
> +               }
> +
> +               /* regulator use is optional */
> +               sprintf(vbus_name, "vbus-p%d", id);
> +               instance_ptr->vbus_supply =
> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
> +                                       vbus_name);
> +               if (IS_ERR(instance_ptr->vbus_supply))
> +                       instance_ptr->vbus_supply = NULL;
> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
> +       }
> +
> +       return 0;
> +
> +put_child:
> +       of_node_put(child);
> +       return ret;
> +}
> +
> +static int cygnus_phy_probe(struct platform_device *pdev)
> +{
> +       struct resource *res;
> +       struct cygnus_phy_driver *phy_driver;
> +       struct phy_provider *phy_provider;
> +       int i, ret;
> +       u32 reg_val;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +
> +       /* allocate memory for each phy instance */
> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
> +                                 GFP_KERNEL);
> +       if (!phy_driver)
> +               return -ENOMEM;
> +
> +       phy_driver->num_phys = of_get_child_count(node);
> +
> +       if (phy_driver->num_phys == 0) {
> +               dev_err(dev, "PHY no child node\n");
> +               return -ENODEV;
> +       }
> +
> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
> +                                            sizeof(struct cygnus_phy_instance),
> +                                            GFP_KERNEL);
> +       if (!phy_driver->instances)
> +               return -ENOMEM;
> +
> +       phy_driver->pdev = pdev;
> +       platform_set_drvdata(pdev, phy_driver);
> +
> +       ret = cygnus_phy_instance_create(phy_driver);
> +       if (ret)
> +               return ret;
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                          "crmu-usbphy-aon-ctrl");
> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                          "cdru-usbphy");
> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs);
> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
> +                  phy_driver->usb2d_idm_regs);
> +       phy_driver->idm_host_enabled = false;
> +
> +       /* Shutdown all ports. They can be powered up as required */
> +       cygnus_phy_shutdown_all(phy_driver);
> +
> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
> +       if (IS_ERR(phy_provider)) {
> +               dev_err(dev, "Failed to register as phy provider\n");
> +               ret = PTR_ERR(phy_provider);
> +               return ret;
> +       }
> +
> +       for (i = 0; i < phy_driver->num_phys; i++) {
> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
> +                       ret = cygnus_register_extcon_notifiers(
> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
> +                       if (ret) {
> +                               dev_err(dev, "Failed to register notifier\n");
> +                               return ret;
> +                       }
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id cygnus_phy_dt_ids[] = {
> +       { .compatible = "brcm,cygnus-usb-phy", },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
> +
> +static struct platform_driver cygnus_phy_driver = {
> +       .probe = cygnus_phy_probe,
> +       .driver = {
> +               .name = "bcm-cygnus-usbphy",
> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
> +       },
> +};
> +module_platform_driver(cygnus_phy_driver);
> +
> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
> +MODULE_LICENSE("GPL v2");
> --
> 1.9.1
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08  7:52     ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:52 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, Chanwoo Choi
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, bcm-kernel-feedback-list,
	Raveendra Padasalagi

Hi,

Adding Chanwoo Choi to review extcon API's.

-Raveendra
On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
<raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
> Add driver for Broadcom's USB phy controller's used in Cygnus
> familyof SoC. Cygnus has three USB phy controller's, port 0,
> port 1 provides USB host functionality and port 2 can be configured
> for host/device role.
>
> Configuration of host/device role for port 2 is achieved based on
> the extcon events, the driver registers to extcon framework to get
> appropriate connect events for Host/Device cables connect/disconnect
> states based on VBUS and ID interrupts.
>
> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
> ---
>  drivers/phy/broadcom/Kconfig              |  14 +
>  drivers/phy/broadcom/Makefile             |   1 +
>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>  3 files changed, 705 insertions(+)
>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>
> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
> index 64fc59c..3179daf 100644
> --- a/drivers/phy/broadcom/Kconfig
> +++ b/drivers/phy/broadcom/Kconfig
> @@ -1,6 +1,20 @@
>  #
>  # Phy drivers for Broadcom platforms
>  #
> +config PHY_BCM_CYGNUS_USB
> +       tristate "Broadcom Cygnus USB PHY support"
> +       depends on OF
> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
> +       select GENERIC_PHY
> +       select EXTCON_USB_GPIO
> +       default ARCH_BCM_CYGNUS
> +       help
> +         Enable this to support three USB PHY's present in Broadcom's
> +         Cygnus chip.
> +
> +         The phys are capable of supporting host mode on all ports and
> +         device mode for port 2.
> +
>  config PHY_CYGNUS_PCIE
>         tristate "Broadcom Cygnus PCIe PHY driver"
>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
> index 4eb82ec..3dec23c 100644
> --- a/drivers/phy/broadcom/Makefile
> +++ b/drivers/phy/broadcom/Makefile
> @@ -1,4 +1,5 @@
>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
> new file mode 100644
> index 0000000..cf957d4
> --- /dev/null
> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
> @@ -0,0 +1,690 @@
> +/*
> + * Copyright 2017 Broadcom
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, version 2, as
> + * published by the Free Software Foundation (the "GPL").
> + *
> + * 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 version 2 (GPLv2) for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * version 2 (GPLv2) along with this source code.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/phy/phy.h>
> +#include <linux/delay.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/extcon.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +
> +/* CDRU Block Register Offsets and bit definitions */
> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
> +
> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
> +
> +/* CRMU Block Register Offsets and bit definitions */
> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
> +
> +/* USB2 IDM Block register Offset and bit definitions */
> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
> +#define SUSPEND_OVERRIDE_0                             BIT(13)
> +#define SUSPEND_OVERRIDE_0_POS                         13
> +#define SUSPEND_OVERRIDE_1                             BIT(14)
> +#define SUSPEND_OVERRIDE_1_POS                         14
> +#define SUSPEND_OVERRIDE_2                             BIT(15)
> +#define SUSPEND_OVERRIDE_2_POS                         15
> +
> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
> +
> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
> +
> +#define PLL_LOCK_RETRY_COUNT                           1000
> +#define MAX_REGULATOR_NAME_LEN                         25
> +#define DUAL_ROLE_PHY                                  2
> +
> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
> +#define USB2_SEL_DEVICE                        0
> +#define USB2_SEL_HOST                  1
> +#define USB2_SEL_IDLE                  2
> +#define USB_CONNECTED                  1
> +#define USB_DISCONNECTED               0
> +#define MAX_NUM_PHYS                   3
> +
> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
> +
> +struct cygnus_phy_instance;
> +
> +struct cygnus_phy_driver {
> +       void __iomem *cdru_usbphy_regs;
> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
> +       void __iomem *usb2h_idm_regs;
> +       void __iomem *usb2d_idm_regs;
> +       int num_phys;
> +       bool idm_host_enabled;
> +       struct cygnus_phy_instance *instances;
> +       int phyto_src_clk;
> +       struct platform_device *pdev;
> +};
> +
> +struct cygnus_phy_instance {
> +       struct cygnus_phy_driver *driver;
> +       struct phy *generic_phy;
> +       int port;
> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
> +       bool power;             /* 1 - powered_on 0 - powered off */
> +       struct regulator *vbus_supply;
> +       spinlock_t lock;
> +       struct extcon_dev *edev;
> +       struct notifier_block   device_nb;
> +       struct notifier_block   host_nb;
> +};
> +
> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
> +                                   struct cygnus_phy_driver *phy_driver)
> +{
> +       /* Wait for the PLL lock status */
> +       int retry = PLL_LOCK_RETRY_COUNT;
> +       u32 reg_val;
> +
> +       do {
> +               udelay(1);
> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
> +                               usb_reg);
> +               if (reg_val & bit_mask)
> +                       return 0;
> +       } while (--retry > 0);
> +
> +       return -EBUSY;
> +}
> +
> +static struct phy *cygnus_phy_xlate(struct device *dev,
> +                                   struct of_phandle_args *args)
> +{
> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
> +       struct cygnus_phy_instance *instance_ptr;
> +
> +       if (!phy_driver)
> +               return ERR_PTR(-EINVAL);
> +
> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
> +               return ERR_PTR(-ENODEV);
> +
> +       if (WARN_ON(args->args_count < 1))
> +               return ERR_PTR(-EINVAL);
> +
> +       instance_ptr = &phy_driver->instances[args->args[0]];
> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
> +               phy_driver->phyto_src_clk = instance_ptr->port;
> +
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               goto ret_p2;
> +       phy_driver->instances[instance_ptr->port].new_state =
> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
> +
> +ret_p2:
> +       return phy_driver->instances[instance_ptr->port].generic_phy;
> +}
> +
> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +       if (enable)
> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +       else
> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +
> +       writel(reg_val, phy_driver->usb2d_idm_regs +
> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs +
> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +
> +       if (enable)
> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
> +       else
> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
> +
> +       writel(reg_val, phy_driver->usb2d_idm_regs +
> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +}
> +
> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
> +                                           u32 src_phy)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
> +
> +       /* Force the clock/reset source phy to not suspend */
> +       reg_val = readl(phy_driver->usb2h_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
> +                       SUSPEND_OVERRIDE_2);
> +
> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
> +
> +       writel(reg_val, phy_driver->usb2h_idm_regs +
> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +}
> +
> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
> +                       phy_driver->cdru_usbphy_regs +
> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
> +               return;
> +       }
> +
> +       /*
> +        * Disable suspend/resume signals to device controller
> +        * when a port is in device mode
> +        */
> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
> +               phy_driver->cdru_usbphy_regs +
> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
> +}
> +
> +static int cygnus_phy_init(struct phy *generic_phy)
> +{
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_phy_dual_role_init(instance_ptr);
> +
> +       return 0;
> +}
> +
> +static int cygnus_phy_shutdown(struct phy *generic_phy)
> +{
> +       u32 reg_val;
> +       int i, ret;
> +       unsigned long flags;
> +       bool power_off_flag = true;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       if (instance_ptr->vbus_supply) {
> +               ret = regulator_disable(instance_ptr->vbus_supply);
> +               if (ret) {
> +                       dev_err(&generic_phy->dev,
> +                                       "Failed to disable regulator\n");
> +                       return ret;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&instance_ptr->lock, flags);
> +
> +       /* power down the phy */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       if (instance_ptr->port == 0) {
> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
> +       } else if (instance_ptr->port == 1) {
> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
> +       } else if (instance_ptr->port == 2) {
> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
> +       }
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +
> +       instance_ptr->power = false;
> +
> +       /* Determine whether all the phy's are powered off */
> +       for (i = 0; i < phy_driver->num_phys; i++) {
> +               if (phy_driver->instances[i].power == true) {
> +                       power_off_flag = false;
> +                       break;
> +               }
> +       }
> +
> +       /* Disable clock to USB device and keep the USB device in reset */
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
> +                                           false);
> +
> +       /*
> +        * Put the host controller into reset state and
> +        * disable clock if all the phy's are powered off
> +        */
> +       if (power_off_flag) {
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               phy_driver->idm_host_enabled = false;
> +       }
> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +       return 0;
> +}
> +
> +static int cygnus_phy_poweron(struct phy *generic_phy)
> +{
> +       int ret;
> +       unsigned long flags;
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +       u32 extcon_event = instance_ptr->new_state;
> +
> +       /*
> +        * Switch on the regulator only if in HOST mode
> +        */
> +       if (instance_ptr->vbus_supply) {
> +               ret = regulator_enable(instance_ptr->vbus_supply);
> +               if (ret) {
> +                       dev_err(&generic_phy->dev,
> +                               "Failed to enable regulator\n");
> +                       goto err_shutdown;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&instance_ptr->lock, flags);
> +       /* Bring the AFE block out of reset to start powering up the PHY */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       if (instance_ptr->port == 0)
> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +       else if (instance_ptr->port == 1)
> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +       else if (instance_ptr->port == 2)
> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +
> +       /* Check for power on and PLL lock */
> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
> +       if (ret < 0) {
> +               dev_err(&generic_phy->dev,
> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
> +                       instance_ptr->port);
> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +               goto err_shutdown;
> +       }
> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
> +       if (ret < 0) {
> +               dev_err(&generic_phy->dev,
> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
> +                       instance_ptr->port);
> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +               goto err_shutdown;
> +       }
> +
> +       instance_ptr->power = true;
> +
> +       /* Enable clock to USB device and take the USB device out of reset */
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
> +
> +       /* Set clock source provider to be the last powered on phy */
> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
> +               cygnus_phy_clk_reset_src_switch(generic_phy,
> +                               instance_ptr->port);
> +
> +       if (phy_driver->idm_host_enabled != true &&
> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
> +               /* Enable clock to USB host and take the host out of reset */
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               phy_driver->idm_host_enabled = true;
> +       }
> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +
> +       return 0;
> +err_shutdown:
> +       cygnus_phy_shutdown(generic_phy);
> +       return ret;
> +}
> +
> +static int usbd_connect_notify(struct notifier_block *self,
> +                              unsigned long event, void *ptr)
> +{
> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
> +                                       struct cygnus_phy_instance, device_nb);
> +
> +       if (event) {
> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
> +               cygnus_phy_dual_role_init(instance_ptr);
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int usbh_connect_notify(struct notifier_block *self,
> +                              unsigned long event, void *ptr)
> +{
> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
> +                                       struct cygnus_phy_instance, host_nb);
> +       if (event) {
> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
> +               cygnus_phy_dual_role_init(instance_ptr);
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int cygnus_register_extcon_notifiers(
> +                               struct cygnus_phy_instance *instance_ptr)
> +{
> +       int ret = 0;
> +       struct device *dev = &instance_ptr->generic_phy->dev;
> +
> +       if (of_property_read_bool(dev->of_node, "extcon")) {
> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
> +               if (IS_ERR(instance_ptr->edev)) {
> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
> +                               return -EPROBE_DEFER;
> +                       ret = PTR_ERR(instance_ptr->edev);
> +                       goto err;
> +               }
> +
> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
> +               ret = devm_extcon_register_notifier(dev,
> +                                               instance_ptr->edev,
> +                                               EXTCON_USB,
> +                                               &instance_ptr->device_nb);
> +               if (ret < 0) {
> +                       dev_err(dev, "Can't register extcon notifier\n");
> +                       goto err;
> +               }
> +
> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
> +                       cygnus_phy_dual_role_init(instance_ptr);
> +               }
> +
> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
> +               ret = devm_extcon_register_notifier(dev,
> +                                               instance_ptr->edev,
> +                                               EXTCON_USB_HOST,
> +                                               &instance_ptr->host_nb);
> +               if (ret < 0) {
> +                       dev_err(dev, "Can't register extcon notifier\n");
> +                       goto err;
> +               }
> +
> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
> +                                       == true) {
> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
> +                       cygnus_phy_dual_role_init(instance_ptr);
> +               }
> +       } else {
> +               dev_err(dev, "Extcon device handle not found\n");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +err:
> +       return ret;
> +}
> +
> +static const struct phy_ops ops = {
> +       .init           = cygnus_phy_init,
> +       .power_on       = cygnus_phy_poweron,
> +       .power_off      = cygnus_phy_shutdown,
> +       .owner = THIS_MODULE,
> +};
> +
> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
> +{
> +       u32 reg_val;
> +
> +       /*
> +        * Shutdown all ports. They can be powered up as
> +        * required
> +        */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +}
> +
> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
> +{
> +       struct device_node *child;
> +       char *vbus_name;
> +       struct platform_device *pdev = phy_driver->pdev;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +       struct cygnus_phy_instance *instance_ptr;
> +       unsigned int id, ret;
> +
> +       for_each_available_child_of_node(node, child) {
> +               if (of_property_read_u32(child, "reg", &id)) {
> +                       dev_err(dev, "missing reg property for %s\n",
> +                               child->name);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               if (id >= MAX_NUM_PHYS) {
> +                       dev_err(dev, "invalid PHY id: %u\n", id);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               instance_ptr = &phy_driver->instances[id];
> +               instance_ptr->driver = phy_driver;
> +               instance_ptr->port = id;
> +               spin_lock_init(&instance_ptr->lock);
> +
> +               if (instance_ptr->generic_phy) {
> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               instance_ptr->generic_phy =
> +                               devm_phy_create(dev, child, &ops);
> +               if (IS_ERR(instance_ptr->generic_phy)) {
> +                       dev_err(dev, "Failed to create usb phy %d", id);
> +                       ret = PTR_ERR(instance_ptr->generic_phy);
> +                       goto put_child;
> +               }
> +
> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
> +                                       MAX_REGULATOR_NAME_LEN,
> +                                       GFP_KERNEL);
> +               if (!vbus_name) {
> +                       ret = -ENOMEM;
> +                       goto put_child;
> +               }
> +
> +               /* regulator use is optional */
> +               sprintf(vbus_name, "vbus-p%d", id);
> +               instance_ptr->vbus_supply =
> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
> +                                       vbus_name);
> +               if (IS_ERR(instance_ptr->vbus_supply))
> +                       instance_ptr->vbus_supply = NULL;
> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
> +       }
> +
> +       return 0;
> +
> +put_child:
> +       of_node_put(child);
> +       return ret;
> +}
> +
> +static int cygnus_phy_probe(struct platform_device *pdev)
> +{
> +       struct resource *res;
> +       struct cygnus_phy_driver *phy_driver;
> +       struct phy_provider *phy_provider;
> +       int i, ret;
> +       u32 reg_val;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +
> +       /* allocate memory for each phy instance */
> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
> +                                 GFP_KERNEL);
> +       if (!phy_driver)
> +               return -ENOMEM;
> +
> +       phy_driver->num_phys = of_get_child_count(node);
> +
> +       if (phy_driver->num_phys == 0) {
> +               dev_err(dev, "PHY no child node\n");
> +               return -ENODEV;
> +       }
> +
> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
> +                                            sizeof(struct cygnus_phy_instance),
> +                                            GFP_KERNEL);
> +       if (!phy_driver->instances)
> +               return -ENOMEM;
> +
> +       phy_driver->pdev = pdev;
> +       platform_set_drvdata(pdev, phy_driver);
> +
> +       ret = cygnus_phy_instance_create(phy_driver);
> +       if (ret)
> +               return ret;
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                          "crmu-usbphy-aon-ctrl");
> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                          "cdru-usbphy");
> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs);
> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
> +                  phy_driver->usb2d_idm_regs);
> +       phy_driver->idm_host_enabled = false;
> +
> +       /* Shutdown all ports. They can be powered up as required */
> +       cygnus_phy_shutdown_all(phy_driver);
> +
> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
> +       if (IS_ERR(phy_provider)) {
> +               dev_err(dev, "Failed to register as phy provider\n");
> +               ret = PTR_ERR(phy_provider);
> +               return ret;
> +       }
> +
> +       for (i = 0; i < phy_driver->num_phys; i++) {
> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
> +                       ret = cygnus_register_extcon_notifiers(
> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
> +                       if (ret) {
> +                               dev_err(dev, "Failed to register notifier\n");
> +                               return ret;
> +                       }
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id cygnus_phy_dt_ids[] = {
> +       { .compatible = "brcm,cygnus-usb-phy", },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
> +
> +static struct platform_driver cygnus_phy_driver = {
> +       .probe = cygnus_phy_probe,
> +       .driver = {
> +               .name = "bcm-cygnus-usbphy",
> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
> +       },
> +};
> +module_platform_driver(cygnus_phy_driver);
> +
> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org");
> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
> +MODULE_LICENSE("GPL v2");
> --
> 1.9.1
>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08  7:52     ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08  7:52 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

Adding Chanwoo Choi to review extcon API's.

-Raveendra
On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
<raveendra.padasalagi@broadcom.com> wrote:
> Add driver for Broadcom's USB phy controller's used in Cygnus
> familyof SoC. Cygnus has three USB phy controller's, port 0,
> port 1 provides USB host functionality and port 2 can be configured
> for host/device role.
>
> Configuration of host/device role for port 2 is achieved based on
> the extcon events, the driver registers to extcon framework to get
> appropriate connect events for Host/Device cables connect/disconnect
> states based on VBUS and ID interrupts.
>
> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
> ---
>  drivers/phy/broadcom/Kconfig              |  14 +
>  drivers/phy/broadcom/Makefile             |   1 +
>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>  3 files changed, 705 insertions(+)
>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>
> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
> index 64fc59c..3179daf 100644
> --- a/drivers/phy/broadcom/Kconfig
> +++ b/drivers/phy/broadcom/Kconfig
> @@ -1,6 +1,20 @@
>  #
>  # Phy drivers for Broadcom platforms
>  #
> +config PHY_BCM_CYGNUS_USB
> +       tristate "Broadcom Cygnus USB PHY support"
> +       depends on OF
> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
> +       select GENERIC_PHY
> +       select EXTCON_USB_GPIO
> +       default ARCH_BCM_CYGNUS
> +       help
> +         Enable this to support three USB PHY's present in Broadcom's
> +         Cygnus chip.
> +
> +         The phys are capable of supporting host mode on all ports and
> +         device mode for port 2.
> +
>  config PHY_CYGNUS_PCIE
>         tristate "Broadcom Cygnus PCIe PHY driver"
>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
> index 4eb82ec..3dec23c 100644
> --- a/drivers/phy/broadcom/Makefile
> +++ b/drivers/phy/broadcom/Makefile
> @@ -1,4 +1,5 @@
>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
> new file mode 100644
> index 0000000..cf957d4
> --- /dev/null
> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
> @@ -0,0 +1,690 @@
> +/*
> + * Copyright 2017 Broadcom
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, version 2, as
> + * published by the Free Software Foundation (the "GPL").
> + *
> + * 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 version 2 (GPLv2) for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * version 2 (GPLv2) along with this source code.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/phy/phy.h>
> +#include <linux/delay.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/extcon.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +
> +/* CDRU Block Register Offsets and bit definitions */
> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
> +
> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
> +
> +/* CRMU Block Register Offsets and bit definitions */
> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
> +
> +/* USB2 IDM Block register Offset and bit definitions */
> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
> +#define SUSPEND_OVERRIDE_0                             BIT(13)
> +#define SUSPEND_OVERRIDE_0_POS                         13
> +#define SUSPEND_OVERRIDE_1                             BIT(14)
> +#define SUSPEND_OVERRIDE_1_POS                         14
> +#define SUSPEND_OVERRIDE_2                             BIT(15)
> +#define SUSPEND_OVERRIDE_2_POS                         15
> +
> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
> +
> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
> +
> +#define PLL_LOCK_RETRY_COUNT                           1000
> +#define MAX_REGULATOR_NAME_LEN                         25
> +#define DUAL_ROLE_PHY                                  2
> +
> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
> +#define USB2_SEL_DEVICE                        0
> +#define USB2_SEL_HOST                  1
> +#define USB2_SEL_IDLE                  2
> +#define USB_CONNECTED                  1
> +#define USB_DISCONNECTED               0
> +#define MAX_NUM_PHYS                   3
> +
> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
> +
> +struct cygnus_phy_instance;
> +
> +struct cygnus_phy_driver {
> +       void __iomem *cdru_usbphy_regs;
> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
> +       void __iomem *usb2h_idm_regs;
> +       void __iomem *usb2d_idm_regs;
> +       int num_phys;
> +       bool idm_host_enabled;
> +       struct cygnus_phy_instance *instances;
> +       int phyto_src_clk;
> +       struct platform_device *pdev;
> +};
> +
> +struct cygnus_phy_instance {
> +       struct cygnus_phy_driver *driver;
> +       struct phy *generic_phy;
> +       int port;
> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
> +       bool power;             /* 1 - powered_on 0 - powered off */
> +       struct regulator *vbus_supply;
> +       spinlock_t lock;
> +       struct extcon_dev *edev;
> +       struct notifier_block   device_nb;
> +       struct notifier_block   host_nb;
> +};
> +
> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
> +                                   struct cygnus_phy_driver *phy_driver)
> +{
> +       /* Wait for the PLL lock status */
> +       int retry = PLL_LOCK_RETRY_COUNT;
> +       u32 reg_val;
> +
> +       do {
> +               udelay(1);
> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
> +                               usb_reg);
> +               if (reg_val & bit_mask)
> +                       return 0;
> +       } while (--retry > 0);
> +
> +       return -EBUSY;
> +}
> +
> +static struct phy *cygnus_phy_xlate(struct device *dev,
> +                                   struct of_phandle_args *args)
> +{
> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
> +       struct cygnus_phy_instance *instance_ptr;
> +
> +       if (!phy_driver)
> +               return ERR_PTR(-EINVAL);
> +
> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
> +               return ERR_PTR(-ENODEV);
> +
> +       if (WARN_ON(args->args_count < 1))
> +               return ERR_PTR(-EINVAL);
> +
> +       instance_ptr = &phy_driver->instances[args->args[0]];
> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
> +               phy_driver->phyto_src_clk = instance_ptr->port;
> +
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               goto ret_p2;
> +       phy_driver->instances[instance_ptr->port].new_state =
> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
> +
> +ret_p2:
> +       return phy_driver->instances[instance_ptr->port].generic_phy;
> +}
> +
> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +       if (enable)
> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +       else
> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +
> +       writel(reg_val, phy_driver->usb2d_idm_regs +
> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs +
> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +
> +       if (enable)
> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
> +       else
> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
> +
> +       writel(reg_val, phy_driver->usb2d_idm_regs +
> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +}
> +
> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
> +                                           u32 src_phy)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
> +
> +       /* Force the clock/reset source phy to not suspend */
> +       reg_val = readl(phy_driver->usb2h_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
> +                       SUSPEND_OVERRIDE_2);
> +
> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
> +
> +       writel(reg_val, phy_driver->usb2h_idm_regs +
> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +}
> +
> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
> +{
> +       u32 reg_val;
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
> +                       phy_driver->cdru_usbphy_regs +
> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
> +               return;
> +       }
> +
> +       /*
> +        * Disable suspend/resume signals to device controller
> +        * when a port is in device mode
> +        */
> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
> +               phy_driver->cdru_usbphy_regs +
> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
> +}
> +
> +static int cygnus_phy_init(struct phy *generic_phy)
> +{
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_phy_dual_role_init(instance_ptr);
> +
> +       return 0;
> +}
> +
> +static int cygnus_phy_shutdown(struct phy *generic_phy)
> +{
> +       u32 reg_val;
> +       int i, ret;
> +       unsigned long flags;
> +       bool power_off_flag = true;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +
> +       if (instance_ptr->vbus_supply) {
> +               ret = regulator_disable(instance_ptr->vbus_supply);
> +               if (ret) {
> +                       dev_err(&generic_phy->dev,
> +                                       "Failed to disable regulator\n");
> +                       return ret;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&instance_ptr->lock, flags);
> +
> +       /* power down the phy */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       if (instance_ptr->port == 0) {
> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
> +       } else if (instance_ptr->port == 1) {
> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
> +       } else if (instance_ptr->port == 2) {
> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
> +       }
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +
> +       instance_ptr->power = false;
> +
> +       /* Determine whether all the phy's are powered off */
> +       for (i = 0; i < phy_driver->num_phys; i++) {
> +               if (phy_driver->instances[i].power == true) {
> +                       power_off_flag = false;
> +                       break;
> +               }
> +       }
> +
> +       /* Disable clock to USB device and keep the USB device in reset */
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
> +                                           false);
> +
> +       /*
> +        * Put the host controller into reset state and
> +        * disable clock if all the phy's are powered off
> +        */
> +       if (power_off_flag) {
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               phy_driver->idm_host_enabled = false;
> +       }
> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +       return 0;
> +}
> +
> +static int cygnus_phy_poweron(struct phy *generic_phy)
> +{
> +       int ret;
> +       unsigned long flags;
> +       u32 reg_val;
> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
> +       u32 extcon_event = instance_ptr->new_state;
> +
> +       /*
> +        * Switch on the regulator only if in HOST mode
> +        */
> +       if (instance_ptr->vbus_supply) {
> +               ret = regulator_enable(instance_ptr->vbus_supply);
> +               if (ret) {
> +                       dev_err(&generic_phy->dev,
> +                               "Failed to enable regulator\n");
> +                       goto err_shutdown;
> +               }
> +       }
> +
> +       spin_lock_irqsave(&instance_ptr->lock, flags);
> +       /* Bring the AFE block out of reset to start powering up the PHY */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       if (instance_ptr->port == 0)
> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +       else if (instance_ptr->port == 1)
> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +       else if (instance_ptr->port == 2)
> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +
> +       /* Check for power on and PLL lock */
> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
> +       if (ret < 0) {
> +               dev_err(&generic_phy->dev,
> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
> +                       instance_ptr->port);
> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +               goto err_shutdown;
> +       }
> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
> +       if (ret < 0) {
> +               dev_err(&generic_phy->dev,
> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
> +                       instance_ptr->port);
> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +               goto err_shutdown;
> +       }
> +
> +       instance_ptr->power = true;
> +
> +       /* Enable clock to USB device and take the USB device out of reset */
> +       if (instance_ptr->port == DUAL_ROLE_PHY)
> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
> +
> +       /* Set clock source provider to be the last powered on phy */
> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
> +               cygnus_phy_clk_reset_src_switch(generic_phy,
> +                               instance_ptr->port);
> +
> +       if (phy_driver->idm_host_enabled != true &&
> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
> +               /* Enable clock to USB host and take the host out of reset */
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
> +
> +               reg_val = readl(phy_driver->usb2h_idm_regs +
> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
> +               writel(reg_val, phy_driver->usb2h_idm_regs +
> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
> +               phy_driver->idm_host_enabled = true;
> +       }
> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
> +
> +       return 0;
> +err_shutdown:
> +       cygnus_phy_shutdown(generic_phy);
> +       return ret;
> +}
> +
> +static int usbd_connect_notify(struct notifier_block *self,
> +                              unsigned long event, void *ptr)
> +{
> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
> +                                       struct cygnus_phy_instance, device_nb);
> +
> +       if (event) {
> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
> +               cygnus_phy_dual_role_init(instance_ptr);
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int usbh_connect_notify(struct notifier_block *self,
> +                              unsigned long event, void *ptr)
> +{
> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
> +                                       struct cygnus_phy_instance, host_nb);
> +       if (event) {
> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
> +               cygnus_phy_dual_role_init(instance_ptr);
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int cygnus_register_extcon_notifiers(
> +                               struct cygnus_phy_instance *instance_ptr)
> +{
> +       int ret = 0;
> +       struct device *dev = &instance_ptr->generic_phy->dev;
> +
> +       if (of_property_read_bool(dev->of_node, "extcon")) {
> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
> +               if (IS_ERR(instance_ptr->edev)) {
> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
> +                               return -EPROBE_DEFER;
> +                       ret = PTR_ERR(instance_ptr->edev);
> +                       goto err;
> +               }
> +
> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
> +               ret = devm_extcon_register_notifier(dev,
> +                                               instance_ptr->edev,
> +                                               EXTCON_USB,
> +                                               &instance_ptr->device_nb);
> +               if (ret < 0) {
> +                       dev_err(dev, "Can't register extcon notifier\n");
> +                       goto err;
> +               }
> +
> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
> +                       cygnus_phy_dual_role_init(instance_ptr);
> +               }
> +
> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
> +               ret = devm_extcon_register_notifier(dev,
> +                                               instance_ptr->edev,
> +                                               EXTCON_USB_HOST,
> +                                               &instance_ptr->host_nb);
> +               if (ret < 0) {
> +                       dev_err(dev, "Can't register extcon notifier\n");
> +                       goto err;
> +               }
> +
> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
> +                                       == true) {
> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
> +                       cygnus_phy_dual_role_init(instance_ptr);
> +               }
> +       } else {
> +               dev_err(dev, "Extcon device handle not found\n");
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +err:
> +       return ret;
> +}
> +
> +static const struct phy_ops ops = {
> +       .init           = cygnus_phy_init,
> +       .power_on       = cygnus_phy_poweron,
> +       .power_off      = cygnus_phy_shutdown,
> +       .owner = THIS_MODULE,
> +};
> +
> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
> +{
> +       u32 reg_val;
> +
> +       /*
> +        * Shutdown all ports. They can be powered up as
> +        * required
> +        */
> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
> +}
> +
> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
> +{
> +       struct device_node *child;
> +       char *vbus_name;
> +       struct platform_device *pdev = phy_driver->pdev;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +       struct cygnus_phy_instance *instance_ptr;
> +       unsigned int id, ret;
> +
> +       for_each_available_child_of_node(node, child) {
> +               if (of_property_read_u32(child, "reg", &id)) {
> +                       dev_err(dev, "missing reg property for %s\n",
> +                               child->name);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               if (id >= MAX_NUM_PHYS) {
> +                       dev_err(dev, "invalid PHY id: %u\n", id);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               instance_ptr = &phy_driver->instances[id];
> +               instance_ptr->driver = phy_driver;
> +               instance_ptr->port = id;
> +               spin_lock_init(&instance_ptr->lock);
> +
> +               if (instance_ptr->generic_phy) {
> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
> +                       ret = -EINVAL;
> +                       goto put_child;
> +               }
> +
> +               instance_ptr->generic_phy =
> +                               devm_phy_create(dev, child, &ops);
> +               if (IS_ERR(instance_ptr->generic_phy)) {
> +                       dev_err(dev, "Failed to create usb phy %d", id);
> +                       ret = PTR_ERR(instance_ptr->generic_phy);
> +                       goto put_child;
> +               }
> +
> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
> +                                       MAX_REGULATOR_NAME_LEN,
> +                                       GFP_KERNEL);
> +               if (!vbus_name) {
> +                       ret = -ENOMEM;
> +                       goto put_child;
> +               }
> +
> +               /* regulator use is optional */
> +               sprintf(vbus_name, "vbus-p%d", id);
> +               instance_ptr->vbus_supply =
> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
> +                                       vbus_name);
> +               if (IS_ERR(instance_ptr->vbus_supply))
> +                       instance_ptr->vbus_supply = NULL;
> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
> +       }
> +
> +       return 0;
> +
> +put_child:
> +       of_node_put(child);
> +       return ret;
> +}
> +
> +static int cygnus_phy_probe(struct platform_device *pdev)
> +{
> +       struct resource *res;
> +       struct cygnus_phy_driver *phy_driver;
> +       struct phy_provider *phy_provider;
> +       int i, ret;
> +       u32 reg_val;
> +       struct device *dev = &pdev->dev;
> +       struct device_node *node = dev->of_node;
> +
> +       /* allocate memory for each phy instance */
> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
> +                                 GFP_KERNEL);
> +       if (!phy_driver)
> +               return -ENOMEM;
> +
> +       phy_driver->num_phys = of_get_child_count(node);
> +
> +       if (phy_driver->num_phys == 0) {
> +               dev_err(dev, "PHY no child node\n");
> +               return -ENODEV;
> +       }
> +
> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
> +                                            sizeof(struct cygnus_phy_instance),
> +                                            GFP_KERNEL);
> +       if (!phy_driver->instances)
> +               return -ENOMEM;
> +
> +       phy_driver->pdev = pdev;
> +       platform_set_drvdata(pdev, phy_driver);
> +
> +       ret = cygnus_phy_instance_create(phy_driver);
> +       if (ret)
> +               return ret;
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                          "crmu-usbphy-aon-ctrl");
> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +                                          "cdru-usbphy");
> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
> +
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
> +
> +       reg_val = readl(phy_driver->usb2d_idm_regs);
> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
> +                  phy_driver->usb2d_idm_regs);
> +       phy_driver->idm_host_enabled = false;
> +
> +       /* Shutdown all ports. They can be powered up as required */
> +       cygnus_phy_shutdown_all(phy_driver);
> +
> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
> +       if (IS_ERR(phy_provider)) {
> +               dev_err(dev, "Failed to register as phy provider\n");
> +               ret = PTR_ERR(phy_provider);
> +               return ret;
> +       }
> +
> +       for (i = 0; i < phy_driver->num_phys; i++) {
> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
> +                       ret = cygnus_register_extcon_notifiers(
> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
> +                       if (ret) {
> +                               dev_err(dev, "Failed to register notifier\n");
> +                               return ret;
> +                       }
> +               }
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id cygnus_phy_dt_ids[] = {
> +       { .compatible = "brcm,cygnus-usb-phy", },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
> +
> +static struct platform_driver cygnus_phy_driver = {
> +       .probe = cygnus_phy_probe,
> +       .driver = {
> +               .name = "bcm-cygnus-usbphy",
> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
> +       },
> +};
> +module_platform_driver(cygnus_phy_driver);
> +
> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
> +MODULE_LICENSE("GPL v2");
> --
> 1.9.1
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08 10:36       ` Chanwoo Choi
  0 siblings, 0 replies; 29+ messages in thread
From: Chanwoo Choi @ 2017-11-08 10:36 UTC (permalink / raw)
  To: Raveendra Padasalagi, Rob Herring, Mark Rutland,
	Kishon Vijay Abraham I, Russell King, Scott Branden, Ray Jui,
	Srinath Mannam, Vikram Prakash, Jon Mason, Florian Fainelli,
	Yoshihiro Shimoda, Raviteja Garimella, Rafal Milecki,
	Arnd Bergmann, Viresh Kumar, Jaehoon Chung
  Cc: devicetree, linux-arm-kernel, linux-kernel, bcm-kernel-feedback-list

Hi,

On 2017년 11월 08일 16:52, Raveendra Padasalagi wrote:
> Hi,
> 
> Adding Chanwoo Choi to review extcon API's.
> 
> -Raveendra
> On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
> <raveendra.padasalagi@broadcom.com> wrote:
>> Add driver for Broadcom's USB phy controller's used in Cygnus
>> familyof SoC. Cygnus has three USB phy controller's, port 0,
>> port 1 provides USB host functionality and port 2 can be configured
>> for host/device role.
>>
>> Configuration of host/device role for port 2 is achieved based on
>> the extcon events, the driver registers to extcon framework to get
>> appropriate connect events for Host/Device cables connect/disconnect
>> states based on VBUS and ID interrupts.
>>
>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>> ---
>>  drivers/phy/broadcom/Kconfig              |  14 +
>>  drivers/phy/broadcom/Makefile             |   1 +
>>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>>  3 files changed, 705 insertions(+)
>>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>
>> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
>> index 64fc59c..3179daf 100644
>> --- a/drivers/phy/broadcom/Kconfig
>> +++ b/drivers/phy/broadcom/Kconfig
>> @@ -1,6 +1,20 @@
>>  #
>>  # Phy drivers for Broadcom platforms
>>  #
>> +config PHY_BCM_CYGNUS_USB
>> +       tristate "Broadcom Cygnus USB PHY support"
>> +       depends on OF
>> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
>> +       select GENERIC_PHY
>> +       select EXTCON_USB_GPIO

As I commented on v1 patch, it is not proper to select the specific
device driver. Instead, you should select the framework as following:

	select EXTCON

And if you want to enable the specific device driver,
you need to add the configuration to the config file
(such as arch/arm64/configs/*defconfig).


>> +       default ARCH_BCM_CYGNUS
>> +       help
>> +         Enable this to support three USB PHY's present in Broadcom's
>> +         Cygnus chip.
>> +
>> +         The phys are capable of supporting host mode on all ports and
>> +         device mode for port 2.
>> +
>>  config PHY_CYGNUS_PCIE
>>         tristate "Broadcom Cygnus PCIe PHY driver"
>>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
>> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
>> index 4eb82ec..3dec23c 100644
>> --- a/drivers/phy/broadcom/Makefile
>> +++ b/drivers/phy/broadcom/Makefile
>> @@ -1,4 +1,5 @@
>>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
>> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
>> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>> new file mode 100644
>> index 0000000..cf957d4
>> --- /dev/null
>> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>> @@ -0,0 +1,690 @@
>> +/*
>> + * Copyright 2017 Broadcom
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License, version 2, as
>> + * published by the Free Software Foundation (the "GPL").
>> + *
>> + * 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 version 2 (GPLv2) for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * version 2 (GPLv2) along with this source code.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/io.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/delay.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/extcon.h>
>> +#include <linux/gpio.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/interrupt.h>
>> +
>> +/* CDRU Block Register Offsets and bit definitions */
>> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
>> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
>> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
>> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
>> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
>> +
>> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
>> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
>> +
>> +/* CRMU Block Register Offsets and bit definitions */
>> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
>> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
>> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
>> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
>> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
>> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
>> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
>> +
>> +/* USB2 IDM Block register Offset and bit definitions */
>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
>> +#define SUSPEND_OVERRIDE_0                             BIT(13)
>> +#define SUSPEND_OVERRIDE_0_POS                         13
>> +#define SUSPEND_OVERRIDE_1                             BIT(14)
>> +#define SUSPEND_OVERRIDE_1_POS                         14
>> +#define SUSPEND_OVERRIDE_2                             BIT(15)
>> +#define SUSPEND_OVERRIDE_2_POS                         15
>> +
>> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
>> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
>> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
>> +
>> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
>> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
>> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
>> +
>> +#define PLL_LOCK_RETRY_COUNT                           1000
>> +#define MAX_REGULATOR_NAME_LEN                         25
>> +#define DUAL_ROLE_PHY                                  2
>> +
>> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
>> +#define USB2_SEL_DEVICE                        0
>> +#define USB2_SEL_HOST                  1
>> +#define USB2_SEL_IDLE                  2
>> +#define USB_CONNECTED                  1
>> +#define USB_DISCONNECTED               0
>> +#define MAX_NUM_PHYS                   3
>> +
>> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
>> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
>> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
>> +
>> +struct cygnus_phy_instance;
>> +
>> +struct cygnus_phy_driver {
>> +       void __iomem *cdru_usbphy_regs;
>> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
>> +       void __iomem *usb2h_idm_regs;
>> +       void __iomem *usb2d_idm_regs;
>> +       int num_phys;
>> +       bool idm_host_enabled;
>> +       struct cygnus_phy_instance *instances;
>> +       int phyto_src_clk;
>> +       struct platform_device *pdev;
>> +};
>> +
>> +struct cygnus_phy_instance {
>> +       struct cygnus_phy_driver *driver;
>> +       struct phy *generic_phy;
>> +       int port;
>> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
>> +       bool power;             /* 1 - powered_on 0 - powered off */
>> +       struct regulator *vbus_supply;
>> +       spinlock_t lock;
>> +       struct extcon_dev *edev;
>> +       struct notifier_block   device_nb;
>> +       struct notifier_block   host_nb;
>> +};
>> +
>> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
>> +                                   struct cygnus_phy_driver *phy_driver)
>> +{
>> +       /* Wait for the PLL lock status */
>> +       int retry = PLL_LOCK_RETRY_COUNT;
>> +       u32 reg_val;
>> +
>> +       do {
>> +               udelay(1);
>> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
>> +                               usb_reg);
>> +               if (reg_val & bit_mask)
>> +                       return 0;
>> +       } while (--retry > 0);
>> +
>> +       return -EBUSY;
>> +}
>> +
>> +static struct phy *cygnus_phy_xlate(struct device *dev,
>> +                                   struct of_phandle_args *args)
>> +{
>> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
>> +       struct cygnus_phy_instance *instance_ptr;
>> +
>> +       if (!phy_driver)
>> +               return ERR_PTR(-EINVAL);
>> +
>> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
>> +               return ERR_PTR(-ENODEV);
>> +
>> +       if (WARN_ON(args->args_count < 1))
>> +               return ERR_PTR(-EINVAL);
>> +
>> +       instance_ptr = &phy_driver->instances[args->args[0]];
>> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
>> +               phy_driver->phyto_src_clk = instance_ptr->port;
>> +
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               goto ret_p2;
>> +       phy_driver->instances[instance_ptr->port].new_state =
>> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +
>> +ret_p2:
>> +       return phy_driver->instances[instance_ptr->port].generic_phy;
>> +}
>> +
>> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +       if (enable)
>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +       else
>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +
>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +
>> +       if (enable)
>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +       else
>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +
>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +}
>> +
>> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
>> +                                           u32 src_phy)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
>> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
>> +
>> +       /* Force the clock/reset source phy to not suspend */
>> +       reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
>> +                       SUSPEND_OVERRIDE_2);
>> +
>> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
>> +
>> +       writel(reg_val, phy_driver->usb2h_idm_regs +
>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +}
>> +
>> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
>> +                       phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>> +               return;
>> +       }
>> +
>> +       /*
>> +        * Disable suspend/resume signals to device controller
>> +        * when a port is in device mode
>> +        */
>> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
>> +               phy_driver->cdru_usbphy_regs +
>> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
>> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>> +}
>> +
>> +static int cygnus_phy_init(struct phy *generic_phy)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +
>> +       return 0;
>> +}
>> +
>> +static int cygnus_phy_shutdown(struct phy *generic_phy)
>> +{
>> +       u32 reg_val;
>> +       int i, ret;
>> +       unsigned long flags;
>> +       bool power_off_flag = true;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       if (instance_ptr->vbus_supply) {
>> +               ret = regulator_disable(instance_ptr->vbus_supply);
>> +               if (ret) {
>> +                       dev_err(&generic_phy->dev,
>> +                                       "Failed to disable regulator\n");
>> +                       return ret;
>> +               }
>> +       }
>> +
>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>> +
>> +       /* power down the phy */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       if (instance_ptr->port == 0) {
>> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
>> +       } else if (instance_ptr->port == 1) {
>> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
>> +       } else if (instance_ptr->port == 2) {
>> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
>> +       }
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +
>> +       instance_ptr->power = false;
>> +
>> +       /* Determine whether all the phy's are powered off */
>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>> +               if (phy_driver->instances[i].power == true) {
>> +                       power_off_flag = false;
>> +                       break;
>> +               }
>> +       }
>> +
>> +       /* Disable clock to USB device and keep the USB device in reset */
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
>> +                                           false);
>> +
>> +       /*
>> +        * Put the host controller into reset state and
>> +        * disable clock if all the phy's are powered off
>> +        */
>> +       if (power_off_flag) {
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               phy_driver->idm_host_enabled = false;
>> +       }
>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +       return 0;
>> +}
>> +
>> +static int cygnus_phy_poweron(struct phy *generic_phy)
>> +{
>> +       int ret;
>> +       unsigned long flags;
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +       u32 extcon_event = instance_ptr->new_state;
>> +
>> +       /*
>> +        * Switch on the regulator only if in HOST mode
>> +        */
>> +       if (instance_ptr->vbus_supply) {
>> +               ret = regulator_enable(instance_ptr->vbus_supply);
>> +               if (ret) {
>> +                       dev_err(&generic_phy->dev,
>> +                               "Failed to enable regulator\n");
>> +                       goto err_shutdown;
>> +               }
>> +       }
>> +
>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>> +       /* Bring the AFE block out of reset to start powering up the PHY */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       if (instance_ptr->port == 0)
>> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +       else if (instance_ptr->port == 1)
>> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +       else if (instance_ptr->port == 2)
>> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +
>> +       /* Check for power on and PLL lock */
>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
>> +       if (ret < 0) {
>> +               dev_err(&generic_phy->dev,
>> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
>> +                       instance_ptr->port);
>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +               goto err_shutdown;
>> +       }
>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
>> +       if (ret < 0) {
>> +               dev_err(&generic_phy->dev,
>> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
>> +                       instance_ptr->port);
>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +               goto err_shutdown;
>> +       }
>> +
>> +       instance_ptr->power = true;
>> +
>> +       /* Enable clock to USB device and take the USB device out of reset */
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
>> +
>> +       /* Set clock source provider to be the last powered on phy */
>> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
>> +               cygnus_phy_clk_reset_src_switch(generic_phy,
>> +                               instance_ptr->port);
>> +
>> +       if (phy_driver->idm_host_enabled != true &&
>> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>> +               /* Enable clock to USB host and take the host out of reset */
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               phy_driver->idm_host_enabled = true;
>> +       }
>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +
>> +       return 0;
>> +err_shutdown:
>> +       cygnus_phy_shutdown(generic_phy);
>> +       return ret;
>> +}
>> +
>> +static int usbd_connect_notify(struct notifier_block *self,
>> +                              unsigned long event, void *ptr)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>> +                                       struct cygnus_phy_instance, device_nb);
>> +
>> +       if (event) {
>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +       }

As I commented on v1 patch, this callback function considers the attached state.
Is it right that you don't consider the detached state?

>> +
>> +       return NOTIFY_OK;
>> +}
>> +
>> +static int usbh_connect_notify(struct notifier_block *self,
>> +                              unsigned long event, void *ptr)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>> +                                       struct cygnus_phy_instance, host_nb);
>> +       if (event) {
>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +       }

ditto.

>> +
>> +       return NOTIFY_OK;
>> +}
>> +
>> +static int cygnus_register_extcon_notifiers(
>> +                               struct cygnus_phy_instance *instance_ptr)
>> +{
>> +       int ret = 0;
>> +       struct device *dev = &instance_ptr->generic_phy->dev;
>> +
>> +       if (of_property_read_bool(dev->of_node, "extcon")) {
>> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
>> +               if (IS_ERR(instance_ptr->edev)) {
>> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
>> +                               return -EPROBE_DEFER;
>> +                       ret = PTR_ERR(instance_ptr->edev);
>> +                       goto err;
>> +               }
>> +
>> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
>> +               ret = devm_extcon_register_notifier(dev,
>> +                                               instance_ptr->edev,
>> +                                               EXTCON_USB,
>> +                                               &instance_ptr->device_nb);
>> +               if (ret < 0) {
>> +                       dev_err(dev, "Can't register extcon notifier\n");
>> +                       goto err;
>> +               }
>> +
>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>> +                       cygnus_phy_dual_role_init(instance_ptr);
>> +               }
>> +
>> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
>> +               ret = devm_extcon_register_notifier(dev,
>> +                                               instance_ptr->edev,
>> +                                               EXTCON_USB_HOST,
>> +                                               &instance_ptr->host_nb);
>> +               if (ret < 0) {
>> +                       dev_err(dev, "Can't register extcon notifier\n");
>> +                       goto err;
>> +               }
>> +
>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
>> +                                       == true) {
>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +                       cygnus_phy_dual_role_init(instance_ptr);
>> +               }
>> +       } else {
>> +               dev_err(dev, "Extcon device handle not found\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       return 0;
>> +err:
>> +       return ret;
>> +}
>> +
>> +static const struct phy_ops ops = {
>> +       .init           = cygnus_phy_init,
>> +       .power_on       = cygnus_phy_poweron,
>> +       .power_off      = cygnus_phy_shutdown,
>> +       .owner = THIS_MODULE,
>> +};
>> +
>> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
>> +{
>> +       u32 reg_val;
>> +
>> +       /*
>> +        * Shutdown all ports. They can be powered up as
>> +        * required
>> +        */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
>> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
>> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +}
>> +
>> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
>> +{
>> +       struct device_node *child;
>> +       char *vbus_name;
>> +       struct platform_device *pdev = phy_driver->pdev;
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *node = dev->of_node;
>> +       struct cygnus_phy_instance *instance_ptr;
>> +       unsigned int id, ret;
>> +
>> +       for_each_available_child_of_node(node, child) {
>> +               if (of_property_read_u32(child, "reg", &id)) {
>> +                       dev_err(dev, "missing reg property for %s\n",
>> +                               child->name);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               if (id >= MAX_NUM_PHYS) {
>> +                       dev_err(dev, "invalid PHY id: %u\n", id);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               instance_ptr = &phy_driver->instances[id];
>> +               instance_ptr->driver = phy_driver;
>> +               instance_ptr->port = id;
>> +               spin_lock_init(&instance_ptr->lock);
>> +
>> +               if (instance_ptr->generic_phy) {
>> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               instance_ptr->generic_phy =
>> +                               devm_phy_create(dev, child, &ops);
>> +               if (IS_ERR(instance_ptr->generic_phy)) {
>> +                       dev_err(dev, "Failed to create usb phy %d", id);
>> +                       ret = PTR_ERR(instance_ptr->generic_phy);
>> +                       goto put_child;
>> +               }
>> +
>> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
>> +                                       MAX_REGULATOR_NAME_LEN,
>> +                                       GFP_KERNEL);
>> +               if (!vbus_name) {
>> +                       ret = -ENOMEM;
>> +                       goto put_child;
>> +               }
>> +
>> +               /* regulator use is optional */
>> +               sprintf(vbus_name, "vbus-p%d", id);
>> +               instance_ptr->vbus_supply =
>> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
>> +                                       vbus_name);
>> +               if (IS_ERR(instance_ptr->vbus_supply))
>> +                       instance_ptr->vbus_supply = NULL;
>> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
>> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
>> +       }
>> +
>> +       return 0;
>> +
>> +put_child:
>> +       of_node_put(child);
>> +       return ret;
>> +}
>> +
>> +static int cygnus_phy_probe(struct platform_device *pdev)
>> +{
>> +       struct resource *res;
>> +       struct cygnus_phy_driver *phy_driver;
>> +       struct phy_provider *phy_provider;
>> +       int i, ret;
>> +       u32 reg_val;
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *node = dev->of_node;
>> +
>> +       /* allocate memory for each phy instance */
>> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
>> +                                 GFP_KERNEL);
>> +       if (!phy_driver)
>> +               return -ENOMEM;
>> +
>> +       phy_driver->num_phys = of_get_child_count(node);
>> +
>> +       if (phy_driver->num_phys == 0) {
>> +               dev_err(dev, "PHY no child node\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
>> +                                            sizeof(struct cygnus_phy_instance),
>> +                                            GFP_KERNEL);
>> +       if (!phy_driver->instances)
>> +               return -ENOMEM;
>> +
>> +       phy_driver->pdev = pdev;
>> +       platform_set_drvdata(pdev, phy_driver);
>> +
>> +       ret = cygnus_phy_instance_create(phy_driver);
>> +       if (ret)
>> +               return ret;
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +                                          "crmu-usbphy-aon-ctrl");
>> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
>> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +                                          "cdru-usbphy");
>> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
>> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
>> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
>> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
>> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
>> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs);
>> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
>> +                  phy_driver->usb2d_idm_regs);
>> +       phy_driver->idm_host_enabled = false;
>> +
>> +       /* Shutdown all ports. They can be powered up as required */
>> +       cygnus_phy_shutdown_all(phy_driver);
>> +
>> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
>> +       if (IS_ERR(phy_provider)) {
>> +               dev_err(dev, "Failed to register as phy provider\n");
>> +               ret = PTR_ERR(phy_provider);
>> +               return ret;
>> +       }
>> +
>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
>> +                       ret = cygnus_register_extcon_notifiers(
>> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
>> +                       if (ret) {
>> +                               dev_err(dev, "Failed to register notifier\n");
>> +                               return ret;
>> +                       }
>> +               }
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id cygnus_phy_dt_ids[] = {
>> +       { .compatible = "brcm,cygnus-usb-phy", },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
>> +
>> +static struct platform_driver cygnus_phy_driver = {
>> +       .probe = cygnus_phy_probe,
>> +       .driver = {
>> +               .name = "bcm-cygnus-usbphy",
>> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
>> +       },
>> +};
>> +module_platform_driver(cygnus_phy_driver);
>> +
>> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
>> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
>> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 1.9.1
>>
> 
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08 10:36       ` Chanwoo Choi
  0 siblings, 0 replies; 29+ messages in thread
From: Chanwoo Choi @ 2017-11-08 10:36 UTC (permalink / raw)
  To: Raveendra Padasalagi, Rob Herring, Mark Rutland,
	Kishon Vijay Abraham I, Russell King, Scott Branden, Ray Jui,
	Srinath Mannam, Vikram Prakash, Jon Mason, Florian Fainelli,
	Yoshihiro Shimoda, Raviteja Garimella, Rafal Milecki,
	Arnd Bergmann, Viresh Kumar, Jaehoon Chung
  Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, bcm-kernel-feedback-list

Hi,

On 2017년 11월 08일 16:52, Raveendra Padasalagi wrote:
> Hi,
> 
> Adding Chanwoo Choi to review extcon API's.
> 
> -Raveendra
> On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
> <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
>> Add driver for Broadcom's USB phy controller's used in Cygnus
>> familyof SoC. Cygnus has three USB phy controller's, port 0,
>> port 1 provides USB host functionality and port 2 can be configured
>> for host/device role.
>>
>> Configuration of host/device role for port 2 is achieved based on
>> the extcon events, the driver registers to extcon framework to get
>> appropriate connect events for Host/Device cables connect/disconnect
>> states based on VBUS and ID interrupts.
>>
>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
>> ---
>>  drivers/phy/broadcom/Kconfig              |  14 +
>>  drivers/phy/broadcom/Makefile             |   1 +
>>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>>  3 files changed, 705 insertions(+)
>>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>
>> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
>> index 64fc59c..3179daf 100644
>> --- a/drivers/phy/broadcom/Kconfig
>> +++ b/drivers/phy/broadcom/Kconfig
>> @@ -1,6 +1,20 @@
>>  #
>>  # Phy drivers for Broadcom platforms
>>  #
>> +config PHY_BCM_CYGNUS_USB
>> +       tristate "Broadcom Cygnus USB PHY support"
>> +       depends on OF
>> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
>> +       select GENERIC_PHY
>> +       select EXTCON_USB_GPIO

As I commented on v1 patch, it is not proper to select the specific
device driver. Instead, you should select the framework as following:

	select EXTCON

And if you want to enable the specific device driver,
you need to add the configuration to the config file
(such as arch/arm64/configs/*defconfig).


>> +       default ARCH_BCM_CYGNUS
>> +       help
>> +         Enable this to support three USB PHY's present in Broadcom's
>> +         Cygnus chip.
>> +
>> +         The phys are capable of supporting host mode on all ports and
>> +         device mode for port 2.
>> +
>>  config PHY_CYGNUS_PCIE
>>         tristate "Broadcom Cygnus PCIe PHY driver"
>>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
>> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
>> index 4eb82ec..3dec23c 100644
>> --- a/drivers/phy/broadcom/Makefile
>> +++ b/drivers/phy/broadcom/Makefile
>> @@ -1,4 +1,5 @@
>>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
>> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
>> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>> new file mode 100644
>> index 0000000..cf957d4
>> --- /dev/null
>> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>> @@ -0,0 +1,690 @@
>> +/*
>> + * Copyright 2017 Broadcom
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License, version 2, as
>> + * published by the Free Software Foundation (the "GPL").
>> + *
>> + * 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 version 2 (GPLv2) for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * version 2 (GPLv2) along with this source code.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/io.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/delay.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/extcon.h>
>> +#include <linux/gpio.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/interrupt.h>
>> +
>> +/* CDRU Block Register Offsets and bit definitions */
>> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
>> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
>> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
>> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
>> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
>> +
>> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
>> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
>> +
>> +/* CRMU Block Register Offsets and bit definitions */
>> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
>> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
>> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
>> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
>> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
>> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
>> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
>> +
>> +/* USB2 IDM Block register Offset and bit definitions */
>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
>> +#define SUSPEND_OVERRIDE_0                             BIT(13)
>> +#define SUSPEND_OVERRIDE_0_POS                         13
>> +#define SUSPEND_OVERRIDE_1                             BIT(14)
>> +#define SUSPEND_OVERRIDE_1_POS                         14
>> +#define SUSPEND_OVERRIDE_2                             BIT(15)
>> +#define SUSPEND_OVERRIDE_2_POS                         15
>> +
>> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
>> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
>> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
>> +
>> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
>> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
>> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
>> +
>> +#define PLL_LOCK_RETRY_COUNT                           1000
>> +#define MAX_REGULATOR_NAME_LEN                         25
>> +#define DUAL_ROLE_PHY                                  2
>> +
>> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
>> +#define USB2_SEL_DEVICE                        0
>> +#define USB2_SEL_HOST                  1
>> +#define USB2_SEL_IDLE                  2
>> +#define USB_CONNECTED                  1
>> +#define USB_DISCONNECTED               0
>> +#define MAX_NUM_PHYS                   3
>> +
>> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
>> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
>> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
>> +
>> +struct cygnus_phy_instance;
>> +
>> +struct cygnus_phy_driver {
>> +       void __iomem *cdru_usbphy_regs;
>> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
>> +       void __iomem *usb2h_idm_regs;
>> +       void __iomem *usb2d_idm_regs;
>> +       int num_phys;
>> +       bool idm_host_enabled;
>> +       struct cygnus_phy_instance *instances;
>> +       int phyto_src_clk;
>> +       struct platform_device *pdev;
>> +};
>> +
>> +struct cygnus_phy_instance {
>> +       struct cygnus_phy_driver *driver;
>> +       struct phy *generic_phy;
>> +       int port;
>> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
>> +       bool power;             /* 1 - powered_on 0 - powered off */
>> +       struct regulator *vbus_supply;
>> +       spinlock_t lock;
>> +       struct extcon_dev *edev;
>> +       struct notifier_block   device_nb;
>> +       struct notifier_block   host_nb;
>> +};
>> +
>> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
>> +                                   struct cygnus_phy_driver *phy_driver)
>> +{
>> +       /* Wait for the PLL lock status */
>> +       int retry = PLL_LOCK_RETRY_COUNT;
>> +       u32 reg_val;
>> +
>> +       do {
>> +               udelay(1);
>> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
>> +                               usb_reg);
>> +               if (reg_val & bit_mask)
>> +                       return 0;
>> +       } while (--retry > 0);
>> +
>> +       return -EBUSY;
>> +}
>> +
>> +static struct phy *cygnus_phy_xlate(struct device *dev,
>> +                                   struct of_phandle_args *args)
>> +{
>> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
>> +       struct cygnus_phy_instance *instance_ptr;
>> +
>> +       if (!phy_driver)
>> +               return ERR_PTR(-EINVAL);
>> +
>> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
>> +               return ERR_PTR(-ENODEV);
>> +
>> +       if (WARN_ON(args->args_count < 1))
>> +               return ERR_PTR(-EINVAL);
>> +
>> +       instance_ptr = &phy_driver->instances[args->args[0]];
>> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
>> +               phy_driver->phyto_src_clk = instance_ptr->port;
>> +
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               goto ret_p2;
>> +       phy_driver->instances[instance_ptr->port].new_state =
>> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +
>> +ret_p2:
>> +       return phy_driver->instances[instance_ptr->port].generic_phy;
>> +}
>> +
>> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +       if (enable)
>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +       else
>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +
>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +
>> +       if (enable)
>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +       else
>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +
>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +}
>> +
>> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
>> +                                           u32 src_phy)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
>> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
>> +
>> +       /* Force the clock/reset source phy to not suspend */
>> +       reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
>> +                       SUSPEND_OVERRIDE_2);
>> +
>> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
>> +
>> +       writel(reg_val, phy_driver->usb2h_idm_regs +
>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +}
>> +
>> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
>> +                       phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>> +               return;
>> +       }
>> +
>> +       /*
>> +        * Disable suspend/resume signals to device controller
>> +        * when a port is in device mode
>> +        */
>> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
>> +               phy_driver->cdru_usbphy_regs +
>> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
>> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>> +}
>> +
>> +static int cygnus_phy_init(struct phy *generic_phy)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +
>> +       return 0;
>> +}
>> +
>> +static int cygnus_phy_shutdown(struct phy *generic_phy)
>> +{
>> +       u32 reg_val;
>> +       int i, ret;
>> +       unsigned long flags;
>> +       bool power_off_flag = true;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       if (instance_ptr->vbus_supply) {
>> +               ret = regulator_disable(instance_ptr->vbus_supply);
>> +               if (ret) {
>> +                       dev_err(&generic_phy->dev,
>> +                                       "Failed to disable regulator\n");
>> +                       return ret;
>> +               }
>> +       }
>> +
>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>> +
>> +       /* power down the phy */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       if (instance_ptr->port == 0) {
>> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
>> +       } else if (instance_ptr->port == 1) {
>> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
>> +       } else if (instance_ptr->port == 2) {
>> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
>> +       }
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +
>> +       instance_ptr->power = false;
>> +
>> +       /* Determine whether all the phy's are powered off */
>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>> +               if (phy_driver->instances[i].power == true) {
>> +                       power_off_flag = false;
>> +                       break;
>> +               }
>> +       }
>> +
>> +       /* Disable clock to USB device and keep the USB device in reset */
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
>> +                                           false);
>> +
>> +       /*
>> +        * Put the host controller into reset state and
>> +        * disable clock if all the phy's are powered off
>> +        */
>> +       if (power_off_flag) {
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               phy_driver->idm_host_enabled = false;
>> +       }
>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +       return 0;
>> +}
>> +
>> +static int cygnus_phy_poweron(struct phy *generic_phy)
>> +{
>> +       int ret;
>> +       unsigned long flags;
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +       u32 extcon_event = instance_ptr->new_state;
>> +
>> +       /*
>> +        * Switch on the regulator only if in HOST mode
>> +        */
>> +       if (instance_ptr->vbus_supply) {
>> +               ret = regulator_enable(instance_ptr->vbus_supply);
>> +               if (ret) {
>> +                       dev_err(&generic_phy->dev,
>> +                               "Failed to enable regulator\n");
>> +                       goto err_shutdown;
>> +               }
>> +       }
>> +
>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>> +       /* Bring the AFE block out of reset to start powering up the PHY */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       if (instance_ptr->port == 0)
>> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +       else if (instance_ptr->port == 1)
>> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +       else if (instance_ptr->port == 2)
>> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +
>> +       /* Check for power on and PLL lock */
>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
>> +       if (ret < 0) {
>> +               dev_err(&generic_phy->dev,
>> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
>> +                       instance_ptr->port);
>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +               goto err_shutdown;
>> +       }
>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
>> +       if (ret < 0) {
>> +               dev_err(&generic_phy->dev,
>> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
>> +                       instance_ptr->port);
>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +               goto err_shutdown;
>> +       }
>> +
>> +       instance_ptr->power = true;
>> +
>> +       /* Enable clock to USB device and take the USB device out of reset */
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
>> +
>> +       /* Set clock source provider to be the last powered on phy */
>> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
>> +               cygnus_phy_clk_reset_src_switch(generic_phy,
>> +                               instance_ptr->port);
>> +
>> +       if (phy_driver->idm_host_enabled != true &&
>> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>> +               /* Enable clock to USB host and take the host out of reset */
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               phy_driver->idm_host_enabled = true;
>> +       }
>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +
>> +       return 0;
>> +err_shutdown:
>> +       cygnus_phy_shutdown(generic_phy);
>> +       return ret;
>> +}
>> +
>> +static int usbd_connect_notify(struct notifier_block *self,
>> +                              unsigned long event, void *ptr)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>> +                                       struct cygnus_phy_instance, device_nb);
>> +
>> +       if (event) {
>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +       }

As I commented on v1 patch, this callback function considers the attached state.
Is it right that you don't consider the detached state?

>> +
>> +       return NOTIFY_OK;
>> +}
>> +
>> +static int usbh_connect_notify(struct notifier_block *self,
>> +                              unsigned long event, void *ptr)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>> +                                       struct cygnus_phy_instance, host_nb);
>> +       if (event) {
>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +       }

ditto.

>> +
>> +       return NOTIFY_OK;
>> +}
>> +
>> +static int cygnus_register_extcon_notifiers(
>> +                               struct cygnus_phy_instance *instance_ptr)
>> +{
>> +       int ret = 0;
>> +       struct device *dev = &instance_ptr->generic_phy->dev;
>> +
>> +       if (of_property_read_bool(dev->of_node, "extcon")) {
>> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
>> +               if (IS_ERR(instance_ptr->edev)) {
>> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
>> +                               return -EPROBE_DEFER;
>> +                       ret = PTR_ERR(instance_ptr->edev);
>> +                       goto err;
>> +               }
>> +
>> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
>> +               ret = devm_extcon_register_notifier(dev,
>> +                                               instance_ptr->edev,
>> +                                               EXTCON_USB,
>> +                                               &instance_ptr->device_nb);
>> +               if (ret < 0) {
>> +                       dev_err(dev, "Can't register extcon notifier\n");
>> +                       goto err;
>> +               }
>> +
>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>> +                       cygnus_phy_dual_role_init(instance_ptr);
>> +               }
>> +
>> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
>> +               ret = devm_extcon_register_notifier(dev,
>> +                                               instance_ptr->edev,
>> +                                               EXTCON_USB_HOST,
>> +                                               &instance_ptr->host_nb);
>> +               if (ret < 0) {
>> +                       dev_err(dev, "Can't register extcon notifier\n");
>> +                       goto err;
>> +               }
>> +
>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
>> +                                       == true) {
>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +                       cygnus_phy_dual_role_init(instance_ptr);
>> +               }
>> +       } else {
>> +               dev_err(dev, "Extcon device handle not found\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       return 0;
>> +err:
>> +       return ret;
>> +}
>> +
>> +static const struct phy_ops ops = {
>> +       .init           = cygnus_phy_init,
>> +       .power_on       = cygnus_phy_poweron,
>> +       .power_off      = cygnus_phy_shutdown,
>> +       .owner = THIS_MODULE,
>> +};
>> +
>> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
>> +{
>> +       u32 reg_val;
>> +
>> +       /*
>> +        * Shutdown all ports. They can be powered up as
>> +        * required
>> +        */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
>> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
>> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +}
>> +
>> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
>> +{
>> +       struct device_node *child;
>> +       char *vbus_name;
>> +       struct platform_device *pdev = phy_driver->pdev;
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *node = dev->of_node;
>> +       struct cygnus_phy_instance *instance_ptr;
>> +       unsigned int id, ret;
>> +
>> +       for_each_available_child_of_node(node, child) {
>> +               if (of_property_read_u32(child, "reg", &id)) {
>> +                       dev_err(dev, "missing reg property for %s\n",
>> +                               child->name);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               if (id >= MAX_NUM_PHYS) {
>> +                       dev_err(dev, "invalid PHY id: %u\n", id);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               instance_ptr = &phy_driver->instances[id];
>> +               instance_ptr->driver = phy_driver;
>> +               instance_ptr->port = id;
>> +               spin_lock_init(&instance_ptr->lock);
>> +
>> +               if (instance_ptr->generic_phy) {
>> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               instance_ptr->generic_phy =
>> +                               devm_phy_create(dev, child, &ops);
>> +               if (IS_ERR(instance_ptr->generic_phy)) {
>> +                       dev_err(dev, "Failed to create usb phy %d", id);
>> +                       ret = PTR_ERR(instance_ptr->generic_phy);
>> +                       goto put_child;
>> +               }
>> +
>> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
>> +                                       MAX_REGULATOR_NAME_LEN,
>> +                                       GFP_KERNEL);
>> +               if (!vbus_name) {
>> +                       ret = -ENOMEM;
>> +                       goto put_child;
>> +               }
>> +
>> +               /* regulator use is optional */
>> +               sprintf(vbus_name, "vbus-p%d", id);
>> +               instance_ptr->vbus_supply =
>> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
>> +                                       vbus_name);
>> +               if (IS_ERR(instance_ptr->vbus_supply))
>> +                       instance_ptr->vbus_supply = NULL;
>> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
>> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
>> +       }
>> +
>> +       return 0;
>> +
>> +put_child:
>> +       of_node_put(child);
>> +       return ret;
>> +}
>> +
>> +static int cygnus_phy_probe(struct platform_device *pdev)
>> +{
>> +       struct resource *res;
>> +       struct cygnus_phy_driver *phy_driver;
>> +       struct phy_provider *phy_provider;
>> +       int i, ret;
>> +       u32 reg_val;
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *node = dev->of_node;
>> +
>> +       /* allocate memory for each phy instance */
>> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
>> +                                 GFP_KERNEL);
>> +       if (!phy_driver)
>> +               return -ENOMEM;
>> +
>> +       phy_driver->num_phys = of_get_child_count(node);
>> +
>> +       if (phy_driver->num_phys == 0) {
>> +               dev_err(dev, "PHY no child node\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
>> +                                            sizeof(struct cygnus_phy_instance),
>> +                                            GFP_KERNEL);
>> +       if (!phy_driver->instances)
>> +               return -ENOMEM;
>> +
>> +       phy_driver->pdev = pdev;
>> +       platform_set_drvdata(pdev, phy_driver);
>> +
>> +       ret = cygnus_phy_instance_create(phy_driver);
>> +       if (ret)
>> +               return ret;
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +                                          "crmu-usbphy-aon-ctrl");
>> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
>> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +                                          "cdru-usbphy");
>> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
>> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
>> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
>> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
>> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
>> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs);
>> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
>> +                  phy_driver->usb2d_idm_regs);
>> +       phy_driver->idm_host_enabled = false;
>> +
>> +       /* Shutdown all ports. They can be powered up as required */
>> +       cygnus_phy_shutdown_all(phy_driver);
>> +
>> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
>> +       if (IS_ERR(phy_provider)) {
>> +               dev_err(dev, "Failed to register as phy provider\n");
>> +               ret = PTR_ERR(phy_provider);
>> +               return ret;
>> +       }
>> +
>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
>> +                       ret = cygnus_register_extcon_notifiers(
>> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
>> +                       if (ret) {
>> +                               dev_err(dev, "Failed to register notifier\n");
>> +                               return ret;
>> +                       }
>> +               }
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id cygnus_phy_dt_ids[] = {
>> +       { .compatible = "brcm,cygnus-usb-phy", },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
>> +
>> +static struct platform_driver cygnus_phy_driver = {
>> +       .probe = cygnus_phy_probe,
>> +       .driver = {
>> +               .name = "bcm-cygnus-usbphy",
>> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
>> +       },
>> +};
>> +module_platform_driver(cygnus_phy_driver);
>> +
>> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
>> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org");
>> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 1.9.1
>>
> 
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08 10:36       ` Chanwoo Choi
  0 siblings, 0 replies; 29+ messages in thread
From: Chanwoo Choi @ 2017-11-08 10:36 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On 2017? 11? 08? 16:52, Raveendra Padasalagi wrote:
> Hi,
> 
> Adding Chanwoo Choi to review extcon API's.
> 
> -Raveendra
> On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
> <raveendra.padasalagi@broadcom.com> wrote:
>> Add driver for Broadcom's USB phy controller's used in Cygnus
>> familyof SoC. Cygnus has three USB phy controller's, port 0,
>> port 1 provides USB host functionality and port 2 can be configured
>> for host/device role.
>>
>> Configuration of host/device role for port 2 is achieved based on
>> the extcon events, the driver registers to extcon framework to get
>> appropriate connect events for Host/Device cables connect/disconnect
>> states based on VBUS and ID interrupts.
>>
>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>> ---
>>  drivers/phy/broadcom/Kconfig              |  14 +
>>  drivers/phy/broadcom/Makefile             |   1 +
>>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>>  3 files changed, 705 insertions(+)
>>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>
>> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
>> index 64fc59c..3179daf 100644
>> --- a/drivers/phy/broadcom/Kconfig
>> +++ b/drivers/phy/broadcom/Kconfig
>> @@ -1,6 +1,20 @@
>>  #
>>  # Phy drivers for Broadcom platforms
>>  #
>> +config PHY_BCM_CYGNUS_USB
>> +       tristate "Broadcom Cygnus USB PHY support"
>> +       depends on OF
>> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
>> +       select GENERIC_PHY
>> +       select EXTCON_USB_GPIO

As I commented on v1 patch, it is not proper to select the specific
device driver. Instead, you should select the framework as following:

	select EXTCON

And if you want to enable the specific device driver,
you need to add the configuration to the config file
(such as arch/arm64/configs/*defconfig).


>> +       default ARCH_BCM_CYGNUS
>> +       help
>> +         Enable this to support three USB PHY's present in Broadcom's
>> +         Cygnus chip.
>> +
>> +         The phys are capable of supporting host mode on all ports and
>> +         device mode for port 2.
>> +
>>  config PHY_CYGNUS_PCIE
>>         tristate "Broadcom Cygnus PCIe PHY driver"
>>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
>> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
>> index 4eb82ec..3dec23c 100644
>> --- a/drivers/phy/broadcom/Makefile
>> +++ b/drivers/phy/broadcom/Makefile
>> @@ -1,4 +1,5 @@
>>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
>> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
>> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>> new file mode 100644
>> index 0000000..cf957d4
>> --- /dev/null
>> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>> @@ -0,0 +1,690 @@
>> +/*
>> + * Copyright 2017 Broadcom
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License, version 2, as
>> + * published by the Free Software Foundation (the "GPL").
>> + *
>> + * 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 version 2 (GPLv2) for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * version 2 (GPLv2) along with this source code.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/io.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/phy/phy.h>
>> +#include <linux/delay.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/extcon.h>
>> +#include <linux/gpio.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/interrupt.h>
>> +
>> +/* CDRU Block Register Offsets and bit definitions */
>> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
>> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
>> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
>> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
>> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
>> +
>> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
>> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
>> +
>> +/* CRMU Block Register Offsets and bit definitions */
>> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
>> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
>> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
>> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
>> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
>> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
>> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
>> +
>> +/* USB2 IDM Block register Offset and bit definitions */
>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
>> +#define SUSPEND_OVERRIDE_0                             BIT(13)
>> +#define SUSPEND_OVERRIDE_0_POS                         13
>> +#define SUSPEND_OVERRIDE_1                             BIT(14)
>> +#define SUSPEND_OVERRIDE_1_POS                         14
>> +#define SUSPEND_OVERRIDE_2                             BIT(15)
>> +#define SUSPEND_OVERRIDE_2_POS                         15
>> +
>> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
>> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
>> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
>> +
>> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
>> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
>> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
>> +
>> +#define PLL_LOCK_RETRY_COUNT                           1000
>> +#define MAX_REGULATOR_NAME_LEN                         25
>> +#define DUAL_ROLE_PHY                                  2
>> +
>> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
>> +#define USB2_SEL_DEVICE                        0
>> +#define USB2_SEL_HOST                  1
>> +#define USB2_SEL_IDLE                  2
>> +#define USB_CONNECTED                  1
>> +#define USB_DISCONNECTED               0
>> +#define MAX_NUM_PHYS                   3
>> +
>> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
>> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
>> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
>> +
>> +struct cygnus_phy_instance;
>> +
>> +struct cygnus_phy_driver {
>> +       void __iomem *cdru_usbphy_regs;
>> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
>> +       void __iomem *usb2h_idm_regs;
>> +       void __iomem *usb2d_idm_regs;
>> +       int num_phys;
>> +       bool idm_host_enabled;
>> +       struct cygnus_phy_instance *instances;
>> +       int phyto_src_clk;
>> +       struct platform_device *pdev;
>> +};
>> +
>> +struct cygnus_phy_instance {
>> +       struct cygnus_phy_driver *driver;
>> +       struct phy *generic_phy;
>> +       int port;
>> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
>> +       bool power;             /* 1 - powered_on 0 - powered off */
>> +       struct regulator *vbus_supply;
>> +       spinlock_t lock;
>> +       struct extcon_dev *edev;
>> +       struct notifier_block   device_nb;
>> +       struct notifier_block   host_nb;
>> +};
>> +
>> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
>> +                                   struct cygnus_phy_driver *phy_driver)
>> +{
>> +       /* Wait for the PLL lock status */
>> +       int retry = PLL_LOCK_RETRY_COUNT;
>> +       u32 reg_val;
>> +
>> +       do {
>> +               udelay(1);
>> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
>> +                               usb_reg);
>> +               if (reg_val & bit_mask)
>> +                       return 0;
>> +       } while (--retry > 0);
>> +
>> +       return -EBUSY;
>> +}
>> +
>> +static struct phy *cygnus_phy_xlate(struct device *dev,
>> +                                   struct of_phandle_args *args)
>> +{
>> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
>> +       struct cygnus_phy_instance *instance_ptr;
>> +
>> +       if (!phy_driver)
>> +               return ERR_PTR(-EINVAL);
>> +
>> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
>> +               return ERR_PTR(-ENODEV);
>> +
>> +       if (WARN_ON(args->args_count < 1))
>> +               return ERR_PTR(-EINVAL);
>> +
>> +       instance_ptr = &phy_driver->instances[args->args[0]];
>> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
>> +               phy_driver->phyto_src_clk = instance_ptr->port;
>> +
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               goto ret_p2;
>> +       phy_driver->instances[instance_ptr->port].new_state =
>> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +
>> +ret_p2:
>> +       return phy_driver->instances[instance_ptr->port].generic_phy;
>> +}
>> +
>> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +       if (enable)
>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +       else
>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +
>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +
>> +       if (enable)
>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +       else
>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +
>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +}
>> +
>> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
>> +                                           u32 src_phy)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
>> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
>> +
>> +       /* Force the clock/reset source phy to not suspend */
>> +       reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
>> +                       SUSPEND_OVERRIDE_2);
>> +
>> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
>> +
>> +       writel(reg_val, phy_driver->usb2h_idm_regs +
>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +}
>> +
>> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
>> +{
>> +       u32 reg_val;
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
>> +                       phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>> +               return;
>> +       }
>> +
>> +       /*
>> +        * Disable suspend/resume signals to device controller
>> +        * when a port is in device mode
>> +        */
>> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
>> +               phy_driver->cdru_usbphy_regs +
>> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
>> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>> +}
>> +
>> +static int cygnus_phy_init(struct phy *generic_phy)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +
>> +       return 0;
>> +}
>> +
>> +static int cygnus_phy_shutdown(struct phy *generic_phy)
>> +{
>> +       u32 reg_val;
>> +       int i, ret;
>> +       unsigned long flags;
>> +       bool power_off_flag = true;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +
>> +       if (instance_ptr->vbus_supply) {
>> +               ret = regulator_disable(instance_ptr->vbus_supply);
>> +               if (ret) {
>> +                       dev_err(&generic_phy->dev,
>> +                                       "Failed to disable regulator\n");
>> +                       return ret;
>> +               }
>> +       }
>> +
>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>> +
>> +       /* power down the phy */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       if (instance_ptr->port == 0) {
>> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
>> +       } else if (instance_ptr->port == 1) {
>> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
>> +       } else if (instance_ptr->port == 2) {
>> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
>> +       }
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +
>> +       instance_ptr->power = false;
>> +
>> +       /* Determine whether all the phy's are powered off */
>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>> +               if (phy_driver->instances[i].power == true) {
>> +                       power_off_flag = false;
>> +                       break;
>> +               }
>> +       }
>> +
>> +       /* Disable clock to USB device and keep the USB device in reset */
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
>> +                                           false);
>> +
>> +       /*
>> +        * Put the host controller into reset state and
>> +        * disable clock if all the phy's are powered off
>> +        */
>> +       if (power_off_flag) {
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               phy_driver->idm_host_enabled = false;
>> +       }
>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +       return 0;
>> +}
>> +
>> +static int cygnus_phy_poweron(struct phy *generic_phy)
>> +{
>> +       int ret;
>> +       unsigned long flags;
>> +       u32 reg_val;
>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>> +       u32 extcon_event = instance_ptr->new_state;
>> +
>> +       /*
>> +        * Switch on the regulator only if in HOST mode
>> +        */
>> +       if (instance_ptr->vbus_supply) {
>> +               ret = regulator_enable(instance_ptr->vbus_supply);
>> +               if (ret) {
>> +                       dev_err(&generic_phy->dev,
>> +                               "Failed to enable regulator\n");
>> +                       goto err_shutdown;
>> +               }
>> +       }
>> +
>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>> +       /* Bring the AFE block out of reset to start powering up the PHY */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       if (instance_ptr->port == 0)
>> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +       else if (instance_ptr->port == 1)
>> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +       else if (instance_ptr->port == 2)
>> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +
>> +       /* Check for power on and PLL lock */
>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
>> +       if (ret < 0) {
>> +               dev_err(&generic_phy->dev,
>> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
>> +                       instance_ptr->port);
>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +               goto err_shutdown;
>> +       }
>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
>> +       if (ret < 0) {
>> +               dev_err(&generic_phy->dev,
>> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
>> +                       instance_ptr->port);
>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +               goto err_shutdown;
>> +       }
>> +
>> +       instance_ptr->power = true;
>> +
>> +       /* Enable clock to USB device and take the USB device out of reset */
>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
>> +
>> +       /* Set clock source provider to be the last powered on phy */
>> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
>> +               cygnus_phy_clk_reset_src_switch(generic_phy,
>> +                               instance_ptr->port);
>> +
>> +       if (phy_driver->idm_host_enabled != true &&
>> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>> +               /* Enable clock to USB host and take the host out of reset */
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>> +
>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>> +               phy_driver->idm_host_enabled = true;
>> +       }
>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>> +
>> +       return 0;
>> +err_shutdown:
>> +       cygnus_phy_shutdown(generic_phy);
>> +       return ret;
>> +}
>> +
>> +static int usbd_connect_notify(struct notifier_block *self,
>> +                              unsigned long event, void *ptr)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>> +                                       struct cygnus_phy_instance, device_nb);
>> +
>> +       if (event) {
>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +       }

As I commented on v1 patch, this callback function considers the attached state.
Is it right that you don't consider the detached state?

>> +
>> +       return NOTIFY_OK;
>> +}
>> +
>> +static int usbh_connect_notify(struct notifier_block *self,
>> +                              unsigned long event, void *ptr)
>> +{
>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>> +                                       struct cygnus_phy_instance, host_nb);
>> +       if (event) {
>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +               cygnus_phy_dual_role_init(instance_ptr);
>> +       }

ditto.

>> +
>> +       return NOTIFY_OK;
>> +}
>> +
>> +static int cygnus_register_extcon_notifiers(
>> +                               struct cygnus_phy_instance *instance_ptr)
>> +{
>> +       int ret = 0;
>> +       struct device *dev = &instance_ptr->generic_phy->dev;
>> +
>> +       if (of_property_read_bool(dev->of_node, "extcon")) {
>> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
>> +               if (IS_ERR(instance_ptr->edev)) {
>> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
>> +                               return -EPROBE_DEFER;
>> +                       ret = PTR_ERR(instance_ptr->edev);
>> +                       goto err;
>> +               }
>> +
>> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
>> +               ret = devm_extcon_register_notifier(dev,
>> +                                               instance_ptr->edev,
>> +                                               EXTCON_USB,
>> +                                               &instance_ptr->device_nb);
>> +               if (ret < 0) {
>> +                       dev_err(dev, "Can't register extcon notifier\n");
>> +                       goto err;
>> +               }
>> +
>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>> +                       cygnus_phy_dual_role_init(instance_ptr);
>> +               }
>> +
>> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
>> +               ret = devm_extcon_register_notifier(dev,
>> +                                               instance_ptr->edev,
>> +                                               EXTCON_USB_HOST,
>> +                                               &instance_ptr->host_nb);
>> +               if (ret < 0) {
>> +                       dev_err(dev, "Can't register extcon notifier\n");
>> +                       goto err;
>> +               }
>> +
>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
>> +                                       == true) {
>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>> +                       cygnus_phy_dual_role_init(instance_ptr);
>> +               }
>> +       } else {
>> +               dev_err(dev, "Extcon device handle not found\n");
>> +               return -EINVAL;
>> +       }
>> +
>> +       return 0;
>> +err:
>> +       return ret;
>> +}
>> +
>> +static const struct phy_ops ops = {
>> +       .init           = cygnus_phy_init,
>> +       .power_on       = cygnus_phy_poweron,
>> +       .power_off      = cygnus_phy_shutdown,
>> +       .owner = THIS_MODULE,
>> +};
>> +
>> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
>> +{
>> +       u32 reg_val;
>> +
>> +       /*
>> +        * Shutdown all ports. They can be powered up as
>> +        * required
>> +        */
>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
>> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
>> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>> +}
>> +
>> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
>> +{
>> +       struct device_node *child;
>> +       char *vbus_name;
>> +       struct platform_device *pdev = phy_driver->pdev;
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *node = dev->of_node;
>> +       struct cygnus_phy_instance *instance_ptr;
>> +       unsigned int id, ret;
>> +
>> +       for_each_available_child_of_node(node, child) {
>> +               if (of_property_read_u32(child, "reg", &id)) {
>> +                       dev_err(dev, "missing reg property for %s\n",
>> +                               child->name);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               if (id >= MAX_NUM_PHYS) {
>> +                       dev_err(dev, "invalid PHY id: %u\n", id);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               instance_ptr = &phy_driver->instances[id];
>> +               instance_ptr->driver = phy_driver;
>> +               instance_ptr->port = id;
>> +               spin_lock_init(&instance_ptr->lock);
>> +
>> +               if (instance_ptr->generic_phy) {
>> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
>> +                       ret = -EINVAL;
>> +                       goto put_child;
>> +               }
>> +
>> +               instance_ptr->generic_phy =
>> +                               devm_phy_create(dev, child, &ops);
>> +               if (IS_ERR(instance_ptr->generic_phy)) {
>> +                       dev_err(dev, "Failed to create usb phy %d", id);
>> +                       ret = PTR_ERR(instance_ptr->generic_phy);
>> +                       goto put_child;
>> +               }
>> +
>> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
>> +                                       MAX_REGULATOR_NAME_LEN,
>> +                                       GFP_KERNEL);
>> +               if (!vbus_name) {
>> +                       ret = -ENOMEM;
>> +                       goto put_child;
>> +               }
>> +
>> +               /* regulator use is optional */
>> +               sprintf(vbus_name, "vbus-p%d", id);
>> +               instance_ptr->vbus_supply =
>> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
>> +                                       vbus_name);
>> +               if (IS_ERR(instance_ptr->vbus_supply))
>> +                       instance_ptr->vbus_supply = NULL;
>> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
>> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
>> +       }
>> +
>> +       return 0;
>> +
>> +put_child:
>> +       of_node_put(child);
>> +       return ret;
>> +}
>> +
>> +static int cygnus_phy_probe(struct platform_device *pdev)
>> +{
>> +       struct resource *res;
>> +       struct cygnus_phy_driver *phy_driver;
>> +       struct phy_provider *phy_provider;
>> +       int i, ret;
>> +       u32 reg_val;
>> +       struct device *dev = &pdev->dev;
>> +       struct device_node *node = dev->of_node;
>> +
>> +       /* allocate memory for each phy instance */
>> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
>> +                                 GFP_KERNEL);
>> +       if (!phy_driver)
>> +               return -ENOMEM;
>> +
>> +       phy_driver->num_phys = of_get_child_count(node);
>> +
>> +       if (phy_driver->num_phys == 0) {
>> +               dev_err(dev, "PHY no child node\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
>> +                                            sizeof(struct cygnus_phy_instance),
>> +                                            GFP_KERNEL);
>> +       if (!phy_driver->instances)
>> +               return -ENOMEM;
>> +
>> +       phy_driver->pdev = pdev;
>> +       platform_set_drvdata(pdev, phy_driver);
>> +
>> +       ret = cygnus_phy_instance_create(phy_driver);
>> +       if (ret)
>> +               return ret;
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +                                          "crmu-usbphy-aon-ctrl");
>> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
>> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +                                          "cdru-usbphy");
>> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
>> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
>> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
>> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
>> +
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
>> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
>> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
>> +
>> +       reg_val = readl(phy_driver->usb2d_idm_regs);
>> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
>> +                  phy_driver->usb2d_idm_regs);
>> +       phy_driver->idm_host_enabled = false;
>> +
>> +       /* Shutdown all ports. They can be powered up as required */
>> +       cygnus_phy_shutdown_all(phy_driver);
>> +
>> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
>> +       if (IS_ERR(phy_provider)) {
>> +               dev_err(dev, "Failed to register as phy provider\n");
>> +               ret = PTR_ERR(phy_provider);
>> +               return ret;
>> +       }
>> +
>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
>> +                       ret = cygnus_register_extcon_notifiers(
>> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
>> +                       if (ret) {
>> +                               dev_err(dev, "Failed to register notifier\n");
>> +                               return ret;
>> +                       }
>> +               }
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id cygnus_phy_dt_ids[] = {
>> +       { .compatible = "brcm,cygnus-usb-phy", },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
>> +
>> +static struct platform_driver cygnus_phy_driver = {
>> +       .probe = cygnus_phy_probe,
>> +       .driver = {
>> +               .name = "bcm-cygnus-usbphy",
>> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
>> +       },
>> +};
>> +module_platform_driver(cygnus_phy_driver);
>> +
>> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
>> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
>> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 1.9.1
>>
> 
> 
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
  2017-11-08 10:36       ` Chanwoo Choi
  (?)
@ 2017-11-08 16:17         ` Raveendra Padasalagi
  -1 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08 16:17 UTC (permalink / raw)
  To: Chanwoo Choi
  Cc: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list

Hi,

On Wed, Nov 8, 2017 at 4:06 PM, Chanwoo Choi <cw00.choi@samsung.com> wrote:
> Hi,
>
> On 2017년 11월 08일 16:52, Raveendra Padasalagi wrote:
>> Hi,
>>
>> Adding Chanwoo Choi to review extcon API's.
>>
>> -Raveendra
>> On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
>> <raveendra.padasalagi@broadcom.com> wrote:
>>> Add driver for Broadcom's USB phy controller's used in Cygnus
>>> familyof SoC. Cygnus has three USB phy controller's, port 0,
>>> port 1 provides USB host functionality and port 2 can be configured
>>> for host/device role.
>>>
>>> Configuration of host/device role for port 2 is achieved based on
>>> the extcon events, the driver registers to extcon framework to get
>>> appropriate connect events for Host/Device cables connect/disconnect
>>> states based on VBUS and ID interrupts.
>>>
>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>> ---
>>>  drivers/phy/broadcom/Kconfig              |  14 +
>>>  drivers/phy/broadcom/Makefile             |   1 +
>>>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>>>  3 files changed, 705 insertions(+)
>>>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>>
>>> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
>>> index 64fc59c..3179daf 100644
>>> --- a/drivers/phy/broadcom/Kconfig
>>> +++ b/drivers/phy/broadcom/Kconfig
>>> @@ -1,6 +1,20 @@
>>>  #
>>>  # Phy drivers for Broadcom platforms
>>>  #
>>> +config PHY_BCM_CYGNUS_USB
>>> +       tristate "Broadcom Cygnus USB PHY support"
>>> +       depends on OF
>>> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
>>> +       select GENERIC_PHY
>>> +       select EXTCON_USB_GPIO
>
> As I commented on v1 patch, it is not proper to select the specific
> device driver. Instead, you should select the framework as following:
>
>         select EXTCON
>
> And if you want to enable the specific device driver,
> you need to add the configuration to the config file
> (such as arch/arm64/configs/*defconfig).
>
Ok, Got confused with the suggestion on previous patch.
Without selecting EXTCON_USB_GPIO, "linux,usb-gpio" driver was not
getting enabled in the build.

It's clear now, I will keep "select EXTCON"  in this Kconfig and send
another patch to enable
EXTCON_USB_GPIO in required defconfig.

>>> +       default ARCH_BCM_CYGNUS
>>> +       help
>>> +         Enable this to support three USB PHY's present in Broadcom's
>>> +         Cygnus chip.
>>> +
>>> +         The phys are capable of supporting host mode on all ports and
>>> +         device mode for port 2.
>>> +
>>>  config PHY_CYGNUS_PCIE
>>>         tristate "Broadcom Cygnus PCIe PHY driver"
>>>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
>>> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
>>> index 4eb82ec..3dec23c 100644
>>> --- a/drivers/phy/broadcom/Makefile
>>> +++ b/drivers/phy/broadcom/Makefile
>>> @@ -1,4 +1,5 @@
>>>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
>>> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>>>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>>>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>>>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
>>> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>> new file mode 100644
>>> index 0000000..cf957d4
>>> --- /dev/null
>>> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>> @@ -0,0 +1,690 @@
>>> +/*
>>> + * Copyright 2017 Broadcom
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License, version 2, as
>>> + * published by the Free Software Foundation (the "GPL").
>>> + *
>>> + * 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 version 2 (GPLv2) for more details.
>>> + *
>>> + * You should have received a copy of the GNU General Public License
>>> + * version 2 (GPLv2) along with this source code.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_address.h>
>>> +#include <linux/phy/phy.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/extcon.h>
>>> +#include <linux/gpio.h>
>>> +#include <linux/gpio/consumer.h>
>>> +#include <linux/interrupt.h>
>>> +
>>> +/* CDRU Block Register Offsets and bit definitions */
>>> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
>>> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
>>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
>>> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
>>> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
>>> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
>>> +
>>> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
>>> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
>>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
>>> +
>>> +/* CRMU Block Register Offsets and bit definitions */
>>> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
>>> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
>>> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
>>> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
>>> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
>>> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
>>> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
>>> +
>>> +/* USB2 IDM Block register Offset and bit definitions */
>>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
>>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
>>> +#define SUSPEND_OVERRIDE_0                             BIT(13)
>>> +#define SUSPEND_OVERRIDE_0_POS                         13
>>> +#define SUSPEND_OVERRIDE_1                             BIT(14)
>>> +#define SUSPEND_OVERRIDE_1_POS                         14
>>> +#define SUSPEND_OVERRIDE_2                             BIT(15)
>>> +#define SUSPEND_OVERRIDE_2_POS                         15
>>> +
>>> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
>>> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
>>> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
>>> +
>>> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
>>> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
>>> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
>>> +
>>> +#define PLL_LOCK_RETRY_COUNT                           1000
>>> +#define MAX_REGULATOR_NAME_LEN                         25
>>> +#define DUAL_ROLE_PHY                                  2
>>> +
>>> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
>>> +#define USB2_SEL_DEVICE                        0
>>> +#define USB2_SEL_HOST                  1
>>> +#define USB2_SEL_IDLE                  2
>>> +#define USB_CONNECTED                  1
>>> +#define USB_DISCONNECTED               0
>>> +#define MAX_NUM_PHYS                   3
>>> +
>>> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
>>> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
>>> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
>>> +
>>> +struct cygnus_phy_instance;
>>> +
>>> +struct cygnus_phy_driver {
>>> +       void __iomem *cdru_usbphy_regs;
>>> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
>>> +       void __iomem *usb2h_idm_regs;
>>> +       void __iomem *usb2d_idm_regs;
>>> +       int num_phys;
>>> +       bool idm_host_enabled;
>>> +       struct cygnus_phy_instance *instances;
>>> +       int phyto_src_clk;
>>> +       struct platform_device *pdev;
>>> +};
>>> +
>>> +struct cygnus_phy_instance {
>>> +       struct cygnus_phy_driver *driver;
>>> +       struct phy *generic_phy;
>>> +       int port;
>>> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
>>> +       bool power;             /* 1 - powered_on 0 - powered off */
>>> +       struct regulator *vbus_supply;
>>> +       spinlock_t lock;
>>> +       struct extcon_dev *edev;
>>> +       struct notifier_block   device_nb;
>>> +       struct notifier_block   host_nb;
>>> +};
>>> +
>>> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
>>> +                                   struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       /* Wait for the PLL lock status */
>>> +       int retry = PLL_LOCK_RETRY_COUNT;
>>> +       u32 reg_val;
>>> +
>>> +       do {
>>> +               udelay(1);
>>> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
>>> +                               usb_reg);
>>> +               if (reg_val & bit_mask)
>>> +                       return 0;
>>> +       } while (--retry > 0);
>>> +
>>> +       return -EBUSY;
>>> +}
>>> +
>>> +static struct phy *cygnus_phy_xlate(struct device *dev,
>>> +                                   struct of_phandle_args *args)
>>> +{
>>> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
>>> +       struct cygnus_phy_instance *instance_ptr;
>>> +
>>> +       if (!phy_driver)
>>> +               return ERR_PTR(-EINVAL);
>>> +
>>> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
>>> +               return ERR_PTR(-ENODEV);
>>> +
>>> +       if (WARN_ON(args->args_count < 1))
>>> +               return ERR_PTR(-EINVAL);
>>> +
>>> +       instance_ptr = &phy_driver->instances[args->args[0]];
>>> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
>>> +               phy_driver->phyto_src_clk = instance_ptr->port;
>>> +
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               goto ret_p2;
>>> +       phy_driver->instances[instance_ptr->port].new_state =
>>> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +
>>> +ret_p2:
>>> +       return phy_driver->instances[instance_ptr->port].generic_phy;
>>> +}
>>> +
>>> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +       if (enable)
>>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +       else
>>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +
>>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>>> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +
>>> +       if (enable)
>>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +       else
>>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +
>>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>>> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +}
>>> +
>>> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
>>> +                                           u32 src_phy)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
>>> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
>>> +
>>> +       /* Force the clock/reset source phy to not suspend */
>>> +       reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
>>> +                       SUSPEND_OVERRIDE_2);
>>> +
>>> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
>>> +
>>> +       writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +}
>>> +
>>> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>>> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
>>> +                       phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>>> +               return;
>>> +       }
>>> +
>>> +       /*
>>> +        * Disable suspend/resume signals to device controller
>>> +        * when a port is in device mode
>>> +        */
>>> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
>>> +               phy_driver->cdru_usbphy_regs +
>>> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>>> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>>> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
>>> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>>> +}
>>> +
>>> +static int cygnus_phy_init(struct phy *generic_phy)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int cygnus_phy_shutdown(struct phy *generic_phy)
>>> +{
>>> +       u32 reg_val;
>>> +       int i, ret;
>>> +       unsigned long flags;
>>> +       bool power_off_flag = true;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       if (instance_ptr->vbus_supply) {
>>> +               ret = regulator_disable(instance_ptr->vbus_supply);
>>> +               if (ret) {
>>> +                       dev_err(&generic_phy->dev,
>>> +                                       "Failed to disable regulator\n");
>>> +                       return ret;
>>> +               }
>>> +       }
>>> +
>>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>>> +
>>> +       /* power down the phy */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       if (instance_ptr->port == 0) {
>>> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
>>> +       } else if (instance_ptr->port == 1) {
>>> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
>>> +       } else if (instance_ptr->port == 2) {
>>> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
>>> +       }
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +
>>> +       instance_ptr->power = false;
>>> +
>>> +       /* Determine whether all the phy's are powered off */
>>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>>> +               if (phy_driver->instances[i].power == true) {
>>> +                       power_off_flag = false;
>>> +                       break;
>>> +               }
>>> +       }
>>> +
>>> +       /* Disable clock to USB device and keep the USB device in reset */
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
>>> +                                           false);
>>> +
>>> +       /*
>>> +        * Put the host controller into reset state and
>>> +        * disable clock if all the phy's are powered off
>>> +        */
>>> +       if (power_off_flag) {
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               phy_driver->idm_host_enabled = false;
>>> +       }
>>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +       return 0;
>>> +}
>>> +
>>> +static int cygnus_phy_poweron(struct phy *generic_phy)
>>> +{
>>> +       int ret;
>>> +       unsigned long flags;
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +       u32 extcon_event = instance_ptr->new_state;
>>> +
>>> +       /*
>>> +        * Switch on the regulator only if in HOST mode
>>> +        */
>>> +       if (instance_ptr->vbus_supply) {
>>> +               ret = regulator_enable(instance_ptr->vbus_supply);
>>> +               if (ret) {
>>> +                       dev_err(&generic_phy->dev,
>>> +                               "Failed to enable regulator\n");
>>> +                       goto err_shutdown;
>>> +               }
>>> +       }
>>> +
>>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>>> +       /* Bring the AFE block out of reset to start powering up the PHY */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       if (instance_ptr->port == 0)
>>> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +       else if (instance_ptr->port == 1)
>>> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +       else if (instance_ptr->port == 2)
>>> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +
>>> +       /* Check for power on and PLL lock */
>>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>>> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
>>> +       if (ret < 0) {
>>> +               dev_err(&generic_phy->dev,
>>> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
>>> +                       instance_ptr->port);
>>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +               goto err_shutdown;
>>> +       }
>>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>>> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
>>> +       if (ret < 0) {
>>> +               dev_err(&generic_phy->dev,
>>> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
>>> +                       instance_ptr->port);
>>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +               goto err_shutdown;
>>> +       }
>>> +
>>> +       instance_ptr->power = true;
>>> +
>>> +       /* Enable clock to USB device and take the USB device out of reset */
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
>>> +
>>> +       /* Set clock source provider to be the last powered on phy */
>>> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
>>> +               cygnus_phy_clk_reset_src_switch(generic_phy,
>>> +                               instance_ptr->port);
>>> +
>>> +       if (phy_driver->idm_host_enabled != true &&
>>> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>>> +               /* Enable clock to USB host and take the host out of reset */
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               phy_driver->idm_host_enabled = true;
>>> +       }
>>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +
>>> +       return 0;
>>> +err_shutdown:
>>> +       cygnus_phy_shutdown(generic_phy);
>>> +       return ret;
>>> +}
>>> +
>>> +static int usbd_connect_notify(struct notifier_block *self,
>>> +                              unsigned long event, void *ptr)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>>> +                                       struct cygnus_phy_instance, device_nb);
>>> +
>>> +       if (event) {
>>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +       }
>
> As I commented on v1 patch, this callback function considers the attached state.
> Is it right that you don't consider the detached state?

I am aware of this, I don't need to handle detached state. Interested in only
device cable connect state as I have to change the role of the PHY only on
attach state. On detach phy is assumed to continue in the previous mode.

>>> +
>>> +       return NOTIFY_OK;
>>> +}
>>> +
>>> +static int usbh_connect_notify(struct notifier_block *self,
>>> +                              unsigned long event, void *ptr)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>>> +                                       struct cygnus_phy_instance, host_nb);
>>> +       if (event) {
>>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +       }
>
> ditto.

I am aware of this, I don't need to handle detached state. Interested in only
host cable connect state as explained above.

>>> +
>>> +       return NOTIFY_OK;
>>> +}
>>> +
>>> +static int cygnus_register_extcon_notifiers(
>>> +                               struct cygnus_phy_instance *instance_ptr)
>>> +{
>>> +       int ret = 0;
>>> +       struct device *dev = &instance_ptr->generic_phy->dev;
>>> +
>>> +       if (of_property_read_bool(dev->of_node, "extcon")) {
>>> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
>>> +               if (IS_ERR(instance_ptr->edev)) {
>>> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
>>> +                               return -EPROBE_DEFER;
>>> +                       ret = PTR_ERR(instance_ptr->edev);
>>> +                       goto err;
>>> +               }
>>> +
>>> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
>>> +               ret = devm_extcon_register_notifier(dev,
>>> +                                               instance_ptr->edev,
>>> +                                               EXTCON_USB,
>>> +                                               &instance_ptr->device_nb);
>>> +               if (ret < 0) {
>>> +                       dev_err(dev, "Can't register extcon notifier\n");
>>> +                       goto err;
>>> +               }
>>> +
>>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
>>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>>> +                       cygnus_phy_dual_role_init(instance_ptr);
>>> +               }
>>> +
>>> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
>>> +               ret = devm_extcon_register_notifier(dev,
>>> +                                               instance_ptr->edev,
>>> +                                               EXTCON_USB_HOST,
>>> +                                               &instance_ptr->host_nb);
>>> +               if (ret < 0) {
>>> +                       dev_err(dev, "Can't register extcon notifier\n");
>>> +                       goto err;
>>> +               }
>>> +
>>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
>>> +                                       == true) {
>>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +                       cygnus_phy_dual_role_init(instance_ptr);
>>> +               }
>>> +       } else {
>>> +               dev_err(dev, "Extcon device handle not found\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       return 0;
>>> +err:
>>> +       return ret;
>>> +}
>>> +
>>> +static const struct phy_ops ops = {
>>> +       .init           = cygnus_phy_init,
>>> +       .power_on       = cygnus_phy_poweron,
>>> +       .power_off      = cygnus_phy_shutdown,
>>> +       .owner = THIS_MODULE,
>>> +};
>>> +
>>> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       u32 reg_val;
>>> +
>>> +       /*
>>> +        * Shutdown all ports. They can be powered up as
>>> +        * required
>>> +        */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
>>> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
>>> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +}
>>> +
>>> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       struct device_node *child;
>>> +       char *vbus_name;
>>> +       struct platform_device *pdev = phy_driver->pdev;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct device_node *node = dev->of_node;
>>> +       struct cygnus_phy_instance *instance_ptr;
>>> +       unsigned int id, ret;
>>> +
>>> +       for_each_available_child_of_node(node, child) {
>>> +               if (of_property_read_u32(child, "reg", &id)) {
>>> +                       dev_err(dev, "missing reg property for %s\n",
>>> +                               child->name);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               if (id >= MAX_NUM_PHYS) {
>>> +                       dev_err(dev, "invalid PHY id: %u\n", id);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               instance_ptr = &phy_driver->instances[id];
>>> +               instance_ptr->driver = phy_driver;
>>> +               instance_ptr->port = id;
>>> +               spin_lock_init(&instance_ptr->lock);
>>> +
>>> +               if (instance_ptr->generic_phy) {
>>> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               instance_ptr->generic_phy =
>>> +                               devm_phy_create(dev, child, &ops);
>>> +               if (IS_ERR(instance_ptr->generic_phy)) {
>>> +                       dev_err(dev, "Failed to create usb phy %d", id);
>>> +                       ret = PTR_ERR(instance_ptr->generic_phy);
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
>>> +                                       MAX_REGULATOR_NAME_LEN,
>>> +                                       GFP_KERNEL);
>>> +               if (!vbus_name) {
>>> +                       ret = -ENOMEM;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               /* regulator use is optional */
>>> +               sprintf(vbus_name, "vbus-p%d", id);
>>> +               instance_ptr->vbus_supply =
>>> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
>>> +                                       vbus_name);
>>> +               if (IS_ERR(instance_ptr->vbus_supply))
>>> +                       instance_ptr->vbus_supply = NULL;
>>> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
>>> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
>>> +       }
>>> +
>>> +       return 0;
>>> +
>>> +put_child:
>>> +       of_node_put(child);
>>> +       return ret;
>>> +}
>>> +
>>> +static int cygnus_phy_probe(struct platform_device *pdev)
>>> +{
>>> +       struct resource *res;
>>> +       struct cygnus_phy_driver *phy_driver;
>>> +       struct phy_provider *phy_provider;
>>> +       int i, ret;
>>> +       u32 reg_val;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct device_node *node = dev->of_node;
>>> +
>>> +       /* allocate memory for each phy instance */
>>> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
>>> +                                 GFP_KERNEL);
>>> +       if (!phy_driver)
>>> +               return -ENOMEM;
>>> +
>>> +       phy_driver->num_phys = of_get_child_count(node);
>>> +
>>> +       if (phy_driver->num_phys == 0) {
>>> +               dev_err(dev, "PHY no child node\n");
>>> +               return -ENODEV;
>>> +       }
>>> +
>>> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
>>> +                                            sizeof(struct cygnus_phy_instance),
>>> +                                            GFP_KERNEL);
>>> +       if (!phy_driver->instances)
>>> +               return -ENOMEM;
>>> +
>>> +       phy_driver->pdev = pdev;
>>> +       platform_set_drvdata(pdev, phy_driver);
>>> +
>>> +       ret = cygnus_phy_instance_create(phy_driver);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +                                          "crmu-usbphy-aon-ctrl");
>>> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
>>> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +                                          "cdru-usbphy");
>>> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
>>> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
>>> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
>>> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
>>> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
>>> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs);
>>> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
>>> +                  phy_driver->usb2d_idm_regs);
>>> +       phy_driver->idm_host_enabled = false;
>>> +
>>> +       /* Shutdown all ports. They can be powered up as required */
>>> +       cygnus_phy_shutdown_all(phy_driver);
>>> +
>>> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
>>> +       if (IS_ERR(phy_provider)) {
>>> +               dev_err(dev, "Failed to register as phy provider\n");
>>> +               ret = PTR_ERR(phy_provider);
>>> +               return ret;
>>> +       }
>>> +
>>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>>> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
>>> +                       ret = cygnus_register_extcon_notifiers(
>>> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
>>> +                       if (ret) {
>>> +                               dev_err(dev, "Failed to register notifier\n");
>>> +                               return ret;
>>> +                       }
>>> +               }
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id cygnus_phy_dt_ids[] = {
>>> +       { .compatible = "brcm,cygnus-usb-phy", },
>>> +       { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
>>> +
>>> +static struct platform_driver cygnus_phy_driver = {
>>> +       .probe = cygnus_phy_probe,
>>> +       .driver = {
>>> +               .name = "bcm-cygnus-usbphy",
>>> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
>>> +       },
>>> +};
>>> +module_platform_driver(cygnus_phy_driver);
>>> +
>>> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
>>> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
>>> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
>>> +MODULE_LICENSE("GPL v2");
>>> --
>>> 1.9.1
>>>
>>
>>
>>
>
>
> --
> Best Regards,
> Chanwoo Choi
> Samsung Electronics

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08 16:17         ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08 16:17 UTC (permalink / raw)
  To: Chanwoo Choi
  Cc: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedbac

Hi,

On Wed, Nov 8, 2017 at 4:06 PM, Chanwoo Choi <cw00.choi@samsung.com> wrote:
> Hi,
>
> On 2017년 11월 08일 16:52, Raveendra Padasalagi wrote:
>> Hi,
>>
>> Adding Chanwoo Choi to review extcon API's.
>>
>> -Raveendra
>> On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
>> <raveendra.padasalagi@broadcom.com> wrote:
>>> Add driver for Broadcom's USB phy controller's used in Cygnus
>>> familyof SoC. Cygnus has three USB phy controller's, port 0,
>>> port 1 provides USB host functionality and port 2 can be configured
>>> for host/device role.
>>>
>>> Configuration of host/device role for port 2 is achieved based on
>>> the extcon events, the driver registers to extcon framework to get
>>> appropriate connect events for Host/Device cables connect/disconnect
>>> states based on VBUS and ID interrupts.
>>>
>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>> ---
>>>  drivers/phy/broadcom/Kconfig              |  14 +
>>>  drivers/phy/broadcom/Makefile             |   1 +
>>>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>>>  3 files changed, 705 insertions(+)
>>>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>>
>>> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
>>> index 64fc59c..3179daf 100644
>>> --- a/drivers/phy/broadcom/Kconfig
>>> +++ b/drivers/phy/broadcom/Kconfig
>>> @@ -1,6 +1,20 @@
>>>  #
>>>  # Phy drivers for Broadcom platforms
>>>  #
>>> +config PHY_BCM_CYGNUS_USB
>>> +       tristate "Broadcom Cygnus USB PHY support"
>>> +       depends on OF
>>> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
>>> +       select GENERIC_PHY
>>> +       select EXTCON_USB_GPIO
>
> As I commented on v1 patch, it is not proper to select the specific
> device driver. Instead, you should select the framework as following:
>
>         select EXTCON
>
> And if you want to enable the specific device driver,
> you need to add the configuration to the config file
> (such as arch/arm64/configs/*defconfig).
>
Ok, Got confused with the suggestion on previous patch.
Without selecting EXTCON_USB_GPIO, "linux,usb-gpio" driver was not
getting enabled in the build.

It's clear now, I will keep "select EXTCON"  in this Kconfig and send
another patch to enable
EXTCON_USB_GPIO in required defconfig.

>>> +       default ARCH_BCM_CYGNUS
>>> +       help
>>> +         Enable this to support three USB PHY's present in Broadcom's
>>> +         Cygnus chip.
>>> +
>>> +         The phys are capable of supporting host mode on all ports and
>>> +         device mode for port 2.
>>> +
>>>  config PHY_CYGNUS_PCIE
>>>         tristate "Broadcom Cygnus PCIe PHY driver"
>>>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
>>> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
>>> index 4eb82ec..3dec23c 100644
>>> --- a/drivers/phy/broadcom/Makefile
>>> +++ b/drivers/phy/broadcom/Makefile
>>> @@ -1,4 +1,5 @@
>>>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
>>> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>>>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>>>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>>>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
>>> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>> new file mode 100644
>>> index 0000000..cf957d4
>>> --- /dev/null
>>> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>> @@ -0,0 +1,690 @@
>>> +/*
>>> + * Copyright 2017 Broadcom
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License, version 2, as
>>> + * published by the Free Software Foundation (the "GPL").
>>> + *
>>> + * 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 version 2 (GPLv2) for more details.
>>> + *
>>> + * You should have received a copy of the GNU General Public License
>>> + * version 2 (GPLv2) along with this source code.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_address.h>
>>> +#include <linux/phy/phy.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/extcon.h>
>>> +#include <linux/gpio.h>
>>> +#include <linux/gpio/consumer.h>
>>> +#include <linux/interrupt.h>
>>> +
>>> +/* CDRU Block Register Offsets and bit definitions */
>>> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
>>> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
>>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
>>> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
>>> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
>>> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
>>> +
>>> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
>>> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
>>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
>>> +
>>> +/* CRMU Block Register Offsets and bit definitions */
>>> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
>>> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
>>> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
>>> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
>>> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
>>> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
>>> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
>>> +
>>> +/* USB2 IDM Block register Offset and bit definitions */
>>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
>>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
>>> +#define SUSPEND_OVERRIDE_0                             BIT(13)
>>> +#define SUSPEND_OVERRIDE_0_POS                         13
>>> +#define SUSPEND_OVERRIDE_1                             BIT(14)
>>> +#define SUSPEND_OVERRIDE_1_POS                         14
>>> +#define SUSPEND_OVERRIDE_2                             BIT(15)
>>> +#define SUSPEND_OVERRIDE_2_POS                         15
>>> +
>>> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
>>> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
>>> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
>>> +
>>> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
>>> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
>>> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
>>> +
>>> +#define PLL_LOCK_RETRY_COUNT                           1000
>>> +#define MAX_REGULATOR_NAME_LEN                         25
>>> +#define DUAL_ROLE_PHY                                  2
>>> +
>>> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
>>> +#define USB2_SEL_DEVICE                        0
>>> +#define USB2_SEL_HOST                  1
>>> +#define USB2_SEL_IDLE                  2
>>> +#define USB_CONNECTED                  1
>>> +#define USB_DISCONNECTED               0
>>> +#define MAX_NUM_PHYS                   3
>>> +
>>> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
>>> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
>>> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
>>> +
>>> +struct cygnus_phy_instance;
>>> +
>>> +struct cygnus_phy_driver {
>>> +       void __iomem *cdru_usbphy_regs;
>>> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
>>> +       void __iomem *usb2h_idm_regs;
>>> +       void __iomem *usb2d_idm_regs;
>>> +       int num_phys;
>>> +       bool idm_host_enabled;
>>> +       struct cygnus_phy_instance *instances;
>>> +       int phyto_src_clk;
>>> +       struct platform_device *pdev;
>>> +};
>>> +
>>> +struct cygnus_phy_instance {
>>> +       struct cygnus_phy_driver *driver;
>>> +       struct phy *generic_phy;
>>> +       int port;
>>> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
>>> +       bool power;             /* 1 - powered_on 0 - powered off */
>>> +       struct regulator *vbus_supply;
>>> +       spinlock_t lock;
>>> +       struct extcon_dev *edev;
>>> +       struct notifier_block   device_nb;
>>> +       struct notifier_block   host_nb;
>>> +};
>>> +
>>> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
>>> +                                   struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       /* Wait for the PLL lock status */
>>> +       int retry = PLL_LOCK_RETRY_COUNT;
>>> +       u32 reg_val;
>>> +
>>> +       do {
>>> +               udelay(1);
>>> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
>>> +                               usb_reg);
>>> +               if (reg_val & bit_mask)
>>> +                       return 0;
>>> +       } while (--retry > 0);
>>> +
>>> +       return -EBUSY;
>>> +}
>>> +
>>> +static struct phy *cygnus_phy_xlate(struct device *dev,
>>> +                                   struct of_phandle_args *args)
>>> +{
>>> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
>>> +       struct cygnus_phy_instance *instance_ptr;
>>> +
>>> +       if (!phy_driver)
>>> +               return ERR_PTR(-EINVAL);
>>> +
>>> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
>>> +               return ERR_PTR(-ENODEV);
>>> +
>>> +       if (WARN_ON(args->args_count < 1))
>>> +               return ERR_PTR(-EINVAL);
>>> +
>>> +       instance_ptr = &phy_driver->instances[args->args[0]];
>>> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
>>> +               phy_driver->phyto_src_clk = instance_ptr->port;
>>> +
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               goto ret_p2;
>>> +       phy_driver->instances[instance_ptr->port].new_state =
>>> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +
>>> +ret_p2:
>>> +       return phy_driver->instances[instance_ptr->port].generic_phy;
>>> +}
>>> +
>>> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +       if (enable)
>>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +       else
>>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +
>>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>>> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +
>>> +       if (enable)
>>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +       else
>>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +
>>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>>> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +}
>>> +
>>> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
>>> +                                           u32 src_phy)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
>>> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
>>> +
>>> +       /* Force the clock/reset source phy to not suspend */
>>> +       reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
>>> +                       SUSPEND_OVERRIDE_2);
>>> +
>>> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
>>> +
>>> +       writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +}
>>> +
>>> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>>> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
>>> +                       phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>>> +               return;
>>> +       }
>>> +
>>> +       /*
>>> +        * Disable suspend/resume signals to device controller
>>> +        * when a port is in device mode
>>> +        */
>>> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
>>> +               phy_driver->cdru_usbphy_regs +
>>> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>>> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>>> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
>>> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>>> +}
>>> +
>>> +static int cygnus_phy_init(struct phy *generic_phy)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int cygnus_phy_shutdown(struct phy *generic_phy)
>>> +{
>>> +       u32 reg_val;
>>> +       int i, ret;
>>> +       unsigned long flags;
>>> +       bool power_off_flag = true;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       if (instance_ptr->vbus_supply) {
>>> +               ret = regulator_disable(instance_ptr->vbus_supply);
>>> +               if (ret) {
>>> +                       dev_err(&generic_phy->dev,
>>> +                                       "Failed to disable regulator\n");
>>> +                       return ret;
>>> +               }
>>> +       }
>>> +
>>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>>> +
>>> +       /* power down the phy */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       if (instance_ptr->port == 0) {
>>> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
>>> +       } else if (instance_ptr->port == 1) {
>>> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
>>> +       } else if (instance_ptr->port == 2) {
>>> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
>>> +       }
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +
>>> +       instance_ptr->power = false;
>>> +
>>> +       /* Determine whether all the phy's are powered off */
>>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>>> +               if (phy_driver->instances[i].power == true) {
>>> +                       power_off_flag = false;
>>> +                       break;
>>> +               }
>>> +       }
>>> +
>>> +       /* Disable clock to USB device and keep the USB device in reset */
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
>>> +                                           false);
>>> +
>>> +       /*
>>> +        * Put the host controller into reset state and
>>> +        * disable clock if all the phy's are powered off
>>> +        */
>>> +       if (power_off_flag) {
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               phy_driver->idm_host_enabled = false;
>>> +       }
>>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +       return 0;
>>> +}
>>> +
>>> +static int cygnus_phy_poweron(struct phy *generic_phy)
>>> +{
>>> +       int ret;
>>> +       unsigned long flags;
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +       u32 extcon_event = instance_ptr->new_state;
>>> +
>>> +       /*
>>> +        * Switch on the regulator only if in HOST mode
>>> +        */
>>> +       if (instance_ptr->vbus_supply) {
>>> +               ret = regulator_enable(instance_ptr->vbus_supply);
>>> +               if (ret) {
>>> +                       dev_err(&generic_phy->dev,
>>> +                               "Failed to enable regulator\n");
>>> +                       goto err_shutdown;
>>> +               }
>>> +       }
>>> +
>>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>>> +       /* Bring the AFE block out of reset to start powering up the PHY */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       if (instance_ptr->port == 0)
>>> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +       else if (instance_ptr->port == 1)
>>> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +       else if (instance_ptr->port == 2)
>>> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +
>>> +       /* Check for power on and PLL lock */
>>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>>> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
>>> +       if (ret < 0) {
>>> +               dev_err(&generic_phy->dev,
>>> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
>>> +                       instance_ptr->port);
>>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +               goto err_shutdown;
>>> +       }
>>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>>> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
>>> +       if (ret < 0) {
>>> +               dev_err(&generic_phy->dev,
>>> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
>>> +                       instance_ptr->port);
>>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +               goto err_shutdown;
>>> +       }
>>> +
>>> +       instance_ptr->power = true;
>>> +
>>> +       /* Enable clock to USB device and take the USB device out of reset */
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
>>> +
>>> +       /* Set clock source provider to be the last powered on phy */
>>> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
>>> +               cygnus_phy_clk_reset_src_switch(generic_phy,
>>> +                               instance_ptr->port);
>>> +
>>> +       if (phy_driver->idm_host_enabled != true &&
>>> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>>> +               /* Enable clock to USB host and take the host out of reset */
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               phy_driver->idm_host_enabled = true;
>>> +       }
>>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +
>>> +       return 0;
>>> +err_shutdown:
>>> +       cygnus_phy_shutdown(generic_phy);
>>> +       return ret;
>>> +}
>>> +
>>> +static int usbd_connect_notify(struct notifier_block *self,
>>> +                              unsigned long event, void *ptr)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>>> +                                       struct cygnus_phy_instance, device_nb);
>>> +
>>> +       if (event) {
>>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +       }
>
> As I commented on v1 patch, this callback function considers the attached state.
> Is it right that you don't consider the detached state?

I am aware of this, I don't need to handle detached state. Interested in only
device cable connect state as I have to change the role of the PHY only on
attach state. On detach phy is assumed to continue in the previous mode.

>>> +
>>> +       return NOTIFY_OK;
>>> +}
>>> +
>>> +static int usbh_connect_notify(struct notifier_block *self,
>>> +                              unsigned long event, void *ptr)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>>> +                                       struct cygnus_phy_instance, host_nb);
>>> +       if (event) {
>>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +       }
>
> ditto.

I am aware of this, I don't need to handle detached state. Interested in only
host cable connect state as explained above.

>>> +
>>> +       return NOTIFY_OK;
>>> +}
>>> +
>>> +static int cygnus_register_extcon_notifiers(
>>> +                               struct cygnus_phy_instance *instance_ptr)
>>> +{
>>> +       int ret = 0;
>>> +       struct device *dev = &instance_ptr->generic_phy->dev;
>>> +
>>> +       if (of_property_read_bool(dev->of_node, "extcon")) {
>>> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
>>> +               if (IS_ERR(instance_ptr->edev)) {
>>> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
>>> +                               return -EPROBE_DEFER;
>>> +                       ret = PTR_ERR(instance_ptr->edev);
>>> +                       goto err;
>>> +               }
>>> +
>>> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
>>> +               ret = devm_extcon_register_notifier(dev,
>>> +                                               instance_ptr->edev,
>>> +                                               EXTCON_USB,
>>> +                                               &instance_ptr->device_nb);
>>> +               if (ret < 0) {
>>> +                       dev_err(dev, "Can't register extcon notifier\n");
>>> +                       goto err;
>>> +               }
>>> +
>>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
>>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>>> +                       cygnus_phy_dual_role_init(instance_ptr);
>>> +               }
>>> +
>>> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
>>> +               ret = devm_extcon_register_notifier(dev,
>>> +                                               instance_ptr->edev,
>>> +                                               EXTCON_USB_HOST,
>>> +                                               &instance_ptr->host_nb);
>>> +               if (ret < 0) {
>>> +                       dev_err(dev, "Can't register extcon notifier\n");
>>> +                       goto err;
>>> +               }
>>> +
>>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
>>> +                                       == true) {
>>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +                       cygnus_phy_dual_role_init(instance_ptr);
>>> +               }
>>> +       } else {
>>> +               dev_err(dev, "Extcon device handle not found\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       return 0;
>>> +err:
>>> +       return ret;
>>> +}
>>> +
>>> +static const struct phy_ops ops = {
>>> +       .init           = cygnus_phy_init,
>>> +       .power_on       = cygnus_phy_poweron,
>>> +       .power_off      = cygnus_phy_shutdown,
>>> +       .owner = THIS_MODULE,
>>> +};
>>> +
>>> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       u32 reg_val;
>>> +
>>> +       /*
>>> +        * Shutdown all ports. They can be powered up as
>>> +        * required
>>> +        */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
>>> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
>>> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +}
>>> +
>>> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       struct device_node *child;
>>> +       char *vbus_name;
>>> +       struct platform_device *pdev = phy_driver->pdev;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct device_node *node = dev->of_node;
>>> +       struct cygnus_phy_instance *instance_ptr;
>>> +       unsigned int id, ret;
>>> +
>>> +       for_each_available_child_of_node(node, child) {
>>> +               if (of_property_read_u32(child, "reg", &id)) {
>>> +                       dev_err(dev, "missing reg property for %s\n",
>>> +                               child->name);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               if (id >= MAX_NUM_PHYS) {
>>> +                       dev_err(dev, "invalid PHY id: %u\n", id);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               instance_ptr = &phy_driver->instances[id];
>>> +               instance_ptr->driver = phy_driver;
>>> +               instance_ptr->port = id;
>>> +               spin_lock_init(&instance_ptr->lock);
>>> +
>>> +               if (instance_ptr->generic_phy) {
>>> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               instance_ptr->generic_phy =
>>> +                               devm_phy_create(dev, child, &ops);
>>> +               if (IS_ERR(instance_ptr->generic_phy)) {
>>> +                       dev_err(dev, "Failed to create usb phy %d", id);
>>> +                       ret = PTR_ERR(instance_ptr->generic_phy);
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
>>> +                                       MAX_REGULATOR_NAME_LEN,
>>> +                                       GFP_KERNEL);
>>> +               if (!vbus_name) {
>>> +                       ret = -ENOMEM;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               /* regulator use is optional */
>>> +               sprintf(vbus_name, "vbus-p%d", id);
>>> +               instance_ptr->vbus_supply =
>>> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
>>> +                                       vbus_name);
>>> +               if (IS_ERR(instance_ptr->vbus_supply))
>>> +                       instance_ptr->vbus_supply = NULL;
>>> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
>>> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
>>> +       }
>>> +
>>> +       return 0;
>>> +
>>> +put_child:
>>> +       of_node_put(child);
>>> +       return ret;
>>> +}
>>> +
>>> +static int cygnus_phy_probe(struct platform_device *pdev)
>>> +{
>>> +       struct resource *res;
>>> +       struct cygnus_phy_driver *phy_driver;
>>> +       struct phy_provider *phy_provider;
>>> +       int i, ret;
>>> +       u32 reg_val;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct device_node *node = dev->of_node;
>>> +
>>> +       /* allocate memory for each phy instance */
>>> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
>>> +                                 GFP_KERNEL);
>>> +       if (!phy_driver)
>>> +               return -ENOMEM;
>>> +
>>> +       phy_driver->num_phys = of_get_child_count(node);
>>> +
>>> +       if (phy_driver->num_phys == 0) {
>>> +               dev_err(dev, "PHY no child node\n");
>>> +               return -ENODEV;
>>> +       }
>>> +
>>> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
>>> +                                            sizeof(struct cygnus_phy_instance),
>>> +                                            GFP_KERNEL);
>>> +       if (!phy_driver->instances)
>>> +               return -ENOMEM;
>>> +
>>> +       phy_driver->pdev = pdev;
>>> +       platform_set_drvdata(pdev, phy_driver);
>>> +
>>> +       ret = cygnus_phy_instance_create(phy_driver);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +                                          "crmu-usbphy-aon-ctrl");
>>> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
>>> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +                                          "cdru-usbphy");
>>> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
>>> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
>>> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
>>> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
>>> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
>>> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs);
>>> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
>>> +                  phy_driver->usb2d_idm_regs);
>>> +       phy_driver->idm_host_enabled = false;
>>> +
>>> +       /* Shutdown all ports. They can be powered up as required */
>>> +       cygnus_phy_shutdown_all(phy_driver);
>>> +
>>> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
>>> +       if (IS_ERR(phy_provider)) {
>>> +               dev_err(dev, "Failed to register as phy provider\n");
>>> +               ret = PTR_ERR(phy_provider);
>>> +               return ret;
>>> +       }
>>> +
>>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>>> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
>>> +                       ret = cygnus_register_extcon_notifiers(
>>> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
>>> +                       if (ret) {
>>> +                               dev_err(dev, "Failed to register notifier\n");
>>> +                               return ret;
>>> +                       }
>>> +               }
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id cygnus_phy_dt_ids[] = {
>>> +       { .compatible = "brcm,cygnus-usb-phy", },
>>> +       { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
>>> +
>>> +static struct platform_driver cygnus_phy_driver = {
>>> +       .probe = cygnus_phy_probe,
>>> +       .driver = {
>>> +               .name = "bcm-cygnus-usbphy",
>>> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
>>> +       },
>>> +};
>>> +module_platform_driver(cygnus_phy_driver);
>>> +
>>> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
>>> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
>>> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
>>> +MODULE_LICENSE("GPL v2");
>>> --
>>> 1.9.1
>>>
>>
>>
>>
>
>
> --
> Best Regards,
> Chanwoo Choi
> Samsung Electronics

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller
@ 2017-11-08 16:17         ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-08 16:17 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Wed, Nov 8, 2017 at 4:06 PM, Chanwoo Choi <cw00.choi@samsung.com> wrote:
> Hi,
>
> On 2017? 11? 08? 16:52, Raveendra Padasalagi wrote:
>> Hi,
>>
>> Adding Chanwoo Choi to review extcon API's.
>>
>> -Raveendra
>> On Wed, Nov 8, 2017 at 1:16 PM, Raveendra Padasalagi
>> <raveendra.padasalagi@broadcom.com> wrote:
>>> Add driver for Broadcom's USB phy controller's used in Cygnus
>>> familyof SoC. Cygnus has three USB phy controller's, port 0,
>>> port 1 provides USB host functionality and port 2 can be configured
>>> for host/device role.
>>>
>>> Configuration of host/device role for port 2 is achieved based on
>>> the extcon events, the driver registers to extcon framework to get
>>> appropriate connect events for Host/Device cables connect/disconnect
>>> states based on VBUS and ID interrupts.
>>>
>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>> ---
>>>  drivers/phy/broadcom/Kconfig              |  14 +
>>>  drivers/phy/broadcom/Makefile             |   1 +
>>>  drivers/phy/broadcom/phy-bcm-cygnus-usb.c | 690 ++++++++++++++++++++++++++++++
>>>  3 files changed, 705 insertions(+)
>>>  create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>>
>>> diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
>>> index 64fc59c..3179daf 100644
>>> --- a/drivers/phy/broadcom/Kconfig
>>> +++ b/drivers/phy/broadcom/Kconfig
>>> @@ -1,6 +1,20 @@
>>>  #
>>>  # Phy drivers for Broadcom platforms
>>>  #
>>> +config PHY_BCM_CYGNUS_USB
>>> +       tristate "Broadcom Cygnus USB PHY support"
>>> +       depends on OF
>>> +       depends on ARCH_BCM_CYGNUS || COMPILE_TEST
>>> +       select GENERIC_PHY
>>> +       select EXTCON_USB_GPIO
>
> As I commented on v1 patch, it is not proper to select the specific
> device driver. Instead, you should select the framework as following:
>
>         select EXTCON
>
> And if you want to enable the specific device driver,
> you need to add the configuration to the config file
> (such as arch/arm64/configs/*defconfig).
>
Ok, Got confused with the suggestion on previous patch.
Without selecting EXTCON_USB_GPIO, "linux,usb-gpio" driver was not
getting enabled in the build.

It's clear now, I will keep "select EXTCON"  in this Kconfig and send
another patch to enable
EXTCON_USB_GPIO in required defconfig.

>>> +       default ARCH_BCM_CYGNUS
>>> +       help
>>> +         Enable this to support three USB PHY's present in Broadcom's
>>> +         Cygnus chip.
>>> +
>>> +         The phys are capable of supporting host mode on all ports and
>>> +         device mode for port 2.
>>> +
>>>  config PHY_CYGNUS_PCIE
>>>         tristate "Broadcom Cygnus PCIe PHY driver"
>>>         depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
>>> diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile
>>> index 4eb82ec..3dec23c 100644
>>> --- a/drivers/phy/broadcom/Makefile
>>> +++ b/drivers/phy/broadcom/Makefile
>>> @@ -1,4 +1,5 @@
>>>  obj-$(CONFIG_PHY_CYGNUS_PCIE)          += phy-bcm-cygnus-pcie.o
>>> +obj-$(CONFIG_PHY_BCM_CYGNUS_USB)       += phy-bcm-cygnus-usb.o
>>>  obj-$(CONFIG_BCM_KONA_USB2_PHY)                += phy-bcm-kona-usb2.o
>>>  obj-$(CONFIG_PHY_BCM_NS_USB2)          += phy-bcm-ns-usb2.o
>>>  obj-$(CONFIG_PHY_BCM_NS_USB3)          += phy-bcm-ns-usb3.o
>>> diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-usb.c b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>> new file mode 100644
>>> index 0000000..cf957d4
>>> --- /dev/null
>>> +++ b/drivers/phy/broadcom/phy-bcm-cygnus-usb.c
>>> @@ -0,0 +1,690 @@
>>> +/*
>>> + * Copyright 2017 Broadcom
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License, version 2, as
>>> + * published by the Free Software Foundation (the "GPL").
>>> + *
>>> + * 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 version 2 (GPLv2) for more details.
>>> + *
>>> + * You should have received a copy of the GNU General Public License
>>> + * version 2 (GPLv2) along with this source code.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_address.h>
>>> +#include <linux/phy/phy.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/regulator/consumer.h>
>>> +#include <linux/extcon.h>
>>> +#include <linux/gpio.h>
>>> +#include <linux/gpio/consumer.h>
>>> +#include <linux/interrupt.h>
>>> +
>>> +/* CDRU Block Register Offsets and bit definitions */
>>> +#define CDRU_USBPHY_CLK_RST_SEL_OFFSET                 0x0
>>> +#define CDRU_USBPHY2_HOST_DEV_SEL_OFFSET               0x4
>>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET                0x5C
>>> +#define CDRU_USBPHY_P0_STATUS_OFFSET                   0x1C
>>> +#define CDRU_USBPHY_P1_STATUS_OFFSET                   0x34
>>> +#define CDRU_USBPHY_P2_STATUS_OFFSET                   0x4C
>>> +
>>> +#define CDRU_USBPHY_USBPHY_ILDO_ON_FLAG                        BIT(1)
>>> +#define CDRU_USBPHY_USBPHY_PLL_LOCK                    BIT(0)
>>> +#define CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE       BIT(0)
>>> +
>>> +/* CRMU Block Register Offsets and bit definitions */
>>> +#define CRMU_USB_PHY_AON_CTRL_OFFSET                   0x0
>>> +#define CRMU_USBPHY_P0_AFE_CORERDY_VDDC                        BIT(1)
>>> +#define CRMU_USBPHY_P0_RESETB                          BIT(2)
>>> +#define CRMU_USBPHY_P1_AFE_CORERDY_VDDC                        BIT(9)
>>> +#define CRMU_USBPHY_P1_RESETB                          BIT(10)
>>> +#define CRMU_USBPHY_P2_AFE_CORERDY_VDDC                        BIT(17)
>>> +#define CRMU_USBPHY_P2_RESETB                          BIT(18)
>>> +
>>> +/* USB2 IDM Block register Offset and bit definitions */
>>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET          0x0408
>>> +#define USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE      BIT(0)
>>> +#define SUSPEND_OVERRIDE_0                             BIT(13)
>>> +#define SUSPEND_OVERRIDE_0_POS                         13
>>> +#define SUSPEND_OVERRIDE_1                             BIT(14)
>>> +#define SUSPEND_OVERRIDE_1_POS                         14
>>> +#define SUSPEND_OVERRIDE_2                             BIT(15)
>>> +#define SUSPEND_OVERRIDE_2_POS                         15
>>> +
>>> +#define USB2_IDM_IDM_RESET_CONTROL_OFFSET              0x0800
>>> +#define USB2_IDM_IDM_RESET_CONTROL__RESET              BIT(0)
>>> +#define USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I      BIT(24)
>>> +
>>> +#define PHY2_DEV_HOST_CTRL_SEL_DEVICE                  0
>>> +#define PHY2_DEV_HOST_CTRL_SEL_HOST                    1
>>> +#define PHY2_DEV_HOST_CTRL_SEL_IDLE                    2
>>> +
>>> +#define PLL_LOCK_RETRY_COUNT                           1000
>>> +#define MAX_REGULATOR_NAME_LEN                         25
>>> +#define DUAL_ROLE_PHY                                  2
>>> +
>>> +#define USBPHY_WQ_DELAY_MS             msecs_to_jiffies(500)
>>> +#define USB2_SEL_DEVICE                        0
>>> +#define USB2_SEL_HOST                  1
>>> +#define USB2_SEL_IDLE                  2
>>> +#define USB_CONNECTED                  1
>>> +#define USB_DISCONNECTED               0
>>> +#define MAX_NUM_PHYS                   3
>>> +
>>> +static const int status_reg[] = {CDRU_USBPHY_P0_STATUS_OFFSET,
>>> +                               CDRU_USBPHY_P1_STATUS_OFFSET,
>>> +                               CDRU_USBPHY_P2_STATUS_OFFSET};
>>> +
>>> +struct cygnus_phy_instance;
>>> +
>>> +struct cygnus_phy_driver {
>>> +       void __iomem *cdru_usbphy_regs;
>>> +       void __iomem *crmu_usbphy_aon_ctrl_regs;
>>> +       void __iomem *usb2h_idm_regs;
>>> +       void __iomem *usb2d_idm_regs;
>>> +       int num_phys;
>>> +       bool idm_host_enabled;
>>> +       struct cygnus_phy_instance *instances;
>>> +       int phyto_src_clk;
>>> +       struct platform_device *pdev;
>>> +};
>>> +
>>> +struct cygnus_phy_instance {
>>> +       struct cygnus_phy_driver *driver;
>>> +       struct phy *generic_phy;
>>> +       int port;
>>> +       int new_state;          /* 1 - Host, 0 - device, 2 - idle*/
>>> +       bool power;             /* 1 - powered_on 0 - powered off */
>>> +       struct regulator *vbus_supply;
>>> +       spinlock_t lock;
>>> +       struct extcon_dev *edev;
>>> +       struct notifier_block   device_nb;
>>> +       struct notifier_block   host_nb;
>>> +};
>>> +
>>> +static inline int phy_pll_lock_stat(u32 usb_reg, int bit_mask,
>>> +                                   struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       /* Wait for the PLL lock status */
>>> +       int retry = PLL_LOCK_RETRY_COUNT;
>>> +       u32 reg_val;
>>> +
>>> +       do {
>>> +               udelay(1);
>>> +               reg_val = readl(phy_driver->cdru_usbphy_regs +
>>> +                               usb_reg);
>>> +               if (reg_val & bit_mask)
>>> +                       return 0;
>>> +       } while (--retry > 0);
>>> +
>>> +       return -EBUSY;
>>> +}
>>> +
>>> +static struct phy *cygnus_phy_xlate(struct device *dev,
>>> +                                   struct of_phandle_args *args)
>>> +{
>>> +       struct cygnus_phy_driver *phy_driver = dev_get_drvdata(dev);
>>> +       struct cygnus_phy_instance *instance_ptr;
>>> +
>>> +       if (!phy_driver)
>>> +               return ERR_PTR(-EINVAL);
>>> +
>>> +       if (WARN_ON(args->args[0] >= phy_driver->num_phys))
>>> +               return ERR_PTR(-ENODEV);
>>> +
>>> +       if (WARN_ON(args->args_count < 1))
>>> +               return ERR_PTR(-EINVAL);
>>> +
>>> +       instance_ptr = &phy_driver->instances[args->args[0]];
>>> +       if (instance_ptr->port > phy_driver->phyto_src_clk)
>>> +               phy_driver->phyto_src_clk = instance_ptr->port;
>>> +
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               goto ret_p2;
>>> +       phy_driver->instances[instance_ptr->port].new_state =
>>> +                                               PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +
>>> +ret_p2:
>>> +       return phy_driver->instances[instance_ptr->port].generic_phy;
>>> +}
>>> +
>>> +static void cygnus_usbp2_dev_clock_init(struct phy *generic_phy, bool enable)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +       if (enable)
>>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +       else
>>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +
>>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs +
>>> +                       USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +
>>> +       if (enable)
>>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +       else
>>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +
>>> +       writel(reg_val, phy_driver->usb2d_idm_regs +
>>> +               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +}
>>> +
>>> +static void cygnus_phy_clk_reset_src_switch(struct phy *generic_phy,
>>> +                                           u32 src_phy)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       writel(src_phy, phy_driver->cdru_usbphy_regs +
>>> +               CDRU_USBPHY_CLK_RST_SEL_OFFSET);
>>> +
>>> +       /* Force the clock/reset source phy to not suspend */
>>> +       reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +       reg_val &= ~(SUSPEND_OVERRIDE_0 | SUSPEND_OVERRIDE_1 |
>>> +                       SUSPEND_OVERRIDE_2);
>>> +
>>> +       reg_val |= BIT(SUSPEND_OVERRIDE_0_POS + src_phy);
>>> +
>>> +       writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +}
>>> +
>>> +static void cygnus_phy_dual_role_init(struct cygnus_phy_instance *instance_ptr)
>>> +{
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       if (instance_ptr->new_state == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>>> +               writel(PHY2_DEV_HOST_CTRL_SEL_HOST,
>>> +                       phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>>> +               return;
>>> +       }
>>> +
>>> +       /*
>>> +        * Disable suspend/resume signals to device controller
>>> +        * when a port is in device mode
>>> +        */
>>> +       writel(PHY2_DEV_HOST_CTRL_SEL_DEVICE,
>>> +               phy_driver->cdru_usbphy_regs +
>>> +               CDRU_USBPHY2_HOST_DEV_SEL_OFFSET);
>>> +       reg_val = readl(phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>>> +       reg_val |= CDRU_USB_DEV_SUSPEND_RESUME_CTRL_DISABLE;
>>> +       writel(reg_val, phy_driver->cdru_usbphy_regs +
>>> +                       CDRU_USB_DEV_SUSPEND_RESUME_CTRL_OFFSET);
>>> +}
>>> +
>>> +static int cygnus_phy_init(struct phy *generic_phy)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int cygnus_phy_shutdown(struct phy *generic_phy)
>>> +{
>>> +       u32 reg_val;
>>> +       int i, ret;
>>> +       unsigned long flags;
>>> +       bool power_off_flag = true;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +
>>> +       if (instance_ptr->vbus_supply) {
>>> +               ret = regulator_disable(instance_ptr->vbus_supply);
>>> +               if (ret) {
>>> +                       dev_err(&generic_phy->dev,
>>> +                                       "Failed to disable regulator\n");
>>> +                       return ret;
>>> +               }
>>> +       }
>>> +
>>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>>> +
>>> +       /* power down the phy */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       if (instance_ptr->port == 0) {
>>> +               reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P0_RESETB;
>>> +       } else if (instance_ptr->port == 1) {
>>> +               reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P1_RESETB;
>>> +       } else if (instance_ptr->port == 2) {
>>> +               reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +               reg_val &= ~CRMU_USBPHY_P2_RESETB;
>>> +       }
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +
>>> +       instance_ptr->power = false;
>>> +
>>> +       /* Determine whether all the phy's are powered off */
>>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>>> +               if (phy_driver->instances[i].power == true) {
>>> +                       power_off_flag = false;
>>> +                       break;
>>> +               }
>>> +       }
>>> +
>>> +       /* Disable clock to USB device and keep the USB device in reset */
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy,
>>> +                                           false);
>>> +
>>> +       /*
>>> +        * Put the host controller into reset state and
>>> +        * disable clock if all the phy's are powered off
>>> +        */
>>> +       if (power_off_flag) {
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                       USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +               reg_val &= ~USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               reg_val |= USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               phy_driver->idm_host_enabled = false;
>>> +       }
>>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +       return 0;
>>> +}
>>> +
>>> +static int cygnus_phy_poweron(struct phy *generic_phy)
>>> +{
>>> +       int ret;
>>> +       unsigned long flags;
>>> +       u32 reg_val;
>>> +       struct cygnus_phy_instance *instance_ptr = phy_get_drvdata(generic_phy);
>>> +       struct cygnus_phy_driver *phy_driver = instance_ptr->driver;
>>> +       u32 extcon_event = instance_ptr->new_state;
>>> +
>>> +       /*
>>> +        * Switch on the regulator only if in HOST mode
>>> +        */
>>> +       if (instance_ptr->vbus_supply) {
>>> +               ret = regulator_enable(instance_ptr->vbus_supply);
>>> +               if (ret) {
>>> +                       dev_err(&generic_phy->dev,
>>> +                               "Failed to enable regulator\n");
>>> +                       goto err_shutdown;
>>> +               }
>>> +       }
>>> +
>>> +       spin_lock_irqsave(&instance_ptr->lock, flags);
>>> +       /* Bring the AFE block out of reset to start powering up the PHY */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       if (instance_ptr->port == 0)
>>> +               reg_val |= CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +       else if (instance_ptr->port == 1)
>>> +               reg_val |= CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +       else if (instance_ptr->port == 2)
>>> +               reg_val |= CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +
>>> +       /* Check for power on and PLL lock */
>>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>>> +                               CDRU_USBPHY_USBPHY_ILDO_ON_FLAG, phy_driver);
>>> +       if (ret < 0) {
>>> +               dev_err(&generic_phy->dev,
>>> +                       "Timed out waiting for USBPHY_ILDO_ON_FLAG on port %d",
>>> +                       instance_ptr->port);
>>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +               goto err_shutdown;
>>> +       }
>>> +       ret = phy_pll_lock_stat(status_reg[instance_ptr->port],
>>> +                               CDRU_USBPHY_USBPHY_PLL_LOCK, phy_driver);
>>> +       if (ret < 0) {
>>> +               dev_err(&generic_phy->dev,
>>> +                       "Timed out waiting for USBPHY_PLL_LOCK on port %d",
>>> +                       instance_ptr->port);
>>> +               spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +               goto err_shutdown;
>>> +       }
>>> +
>>> +       instance_ptr->power = true;
>>> +
>>> +       /* Enable clock to USB device and take the USB device out of reset */
>>> +       if (instance_ptr->port == DUAL_ROLE_PHY)
>>> +               cygnus_usbp2_dev_clock_init(instance_ptr->generic_phy, true);
>>> +
>>> +       /* Set clock source provider to be the last powered on phy */
>>> +       if (instance_ptr->port == phy_driver->phyto_src_clk)
>>> +               cygnus_phy_clk_reset_src_switch(generic_phy,
>>> +                               instance_ptr->port);
>>> +
>>> +       if (phy_driver->idm_host_enabled != true &&
>>> +               extcon_event == PHY2_DEV_HOST_CTRL_SEL_HOST) {
>>> +               /* Enable clock to USB host and take the host out of reset */
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +               reg_val |= USB2_IDM_IDM_IO_CONTROL_DIRECT_CLK_ENABLE;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_IO_CONTROL_DIRECT_OFFSET);
>>> +
>>> +               reg_val = readl(phy_driver->usb2h_idm_regs +
>>> +                               USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               reg_val &= ~USB2_IDM_IDM_RESET_CONTROL__RESET;
>>> +               writel(reg_val, phy_driver->usb2h_idm_regs +
>>> +                                USB2_IDM_IDM_RESET_CONTROL_OFFSET);
>>> +               phy_driver->idm_host_enabled = true;
>>> +       }
>>> +       spin_unlock_irqrestore(&instance_ptr->lock, flags);
>>> +
>>> +       return 0;
>>> +err_shutdown:
>>> +       cygnus_phy_shutdown(generic_phy);
>>> +       return ret;
>>> +}
>>> +
>>> +static int usbd_connect_notify(struct notifier_block *self,
>>> +                              unsigned long event, void *ptr)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>>> +                                       struct cygnus_phy_instance, device_nb);
>>> +
>>> +       if (event) {
>>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +       }
>
> As I commented on v1 patch, this callback function considers the attached state.
> Is it right that you don't consider the detached state?

I am aware of this, I don't need to handle detached state. Interested in only
device cable connect state as I have to change the role of the PHY only on
attach state. On detach phy is assumed to continue in the previous mode.

>>> +
>>> +       return NOTIFY_OK;
>>> +}
>>> +
>>> +static int usbh_connect_notify(struct notifier_block *self,
>>> +                              unsigned long event, void *ptr)
>>> +{
>>> +       struct cygnus_phy_instance *instance_ptr = container_of(self,
>>> +                                       struct cygnus_phy_instance, host_nb);
>>> +       if (event) {
>>> +               instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +               cygnus_phy_dual_role_init(instance_ptr);
>>> +       }
>
> ditto.

I am aware of this, I don't need to handle detached state. Interested in only
host cable connect state as explained above.

>>> +
>>> +       return NOTIFY_OK;
>>> +}
>>> +
>>> +static int cygnus_register_extcon_notifiers(
>>> +                               struct cygnus_phy_instance *instance_ptr)
>>> +{
>>> +       int ret = 0;
>>> +       struct device *dev = &instance_ptr->generic_phy->dev;
>>> +
>>> +       if (of_property_read_bool(dev->of_node, "extcon")) {
>>> +               instance_ptr->edev = extcon_get_edev_by_phandle(dev, 0);
>>> +               if (IS_ERR(instance_ptr->edev)) {
>>> +                       if (PTR_ERR(instance_ptr->edev) == -EPROBE_DEFER)
>>> +                               return -EPROBE_DEFER;
>>> +                       ret = PTR_ERR(instance_ptr->edev);
>>> +                       goto err;
>>> +               }
>>> +
>>> +               instance_ptr->device_nb.notifier_call = usbd_connect_notify;
>>> +               ret = devm_extcon_register_notifier(dev,
>>> +                                               instance_ptr->edev,
>>> +                                               EXTCON_USB,
>>> +                                               &instance_ptr->device_nb);
>>> +               if (ret < 0) {
>>> +                       dev_err(dev, "Can't register extcon notifier\n");
>>> +                       goto err;
>>> +               }
>>> +
>>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB) == true) {
>>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_DEVICE;
>>> +                       cygnus_phy_dual_role_init(instance_ptr);
>>> +               }
>>> +
>>> +               instance_ptr->host_nb.notifier_call = usbh_connect_notify;
>>> +               ret = devm_extcon_register_notifier(dev,
>>> +                                               instance_ptr->edev,
>>> +                                               EXTCON_USB_HOST,
>>> +                                               &instance_ptr->host_nb);
>>> +               if (ret < 0) {
>>> +                       dev_err(dev, "Can't register extcon notifier\n");
>>> +                       goto err;
>>> +               }
>>> +
>>> +               if (extcon_get_state(instance_ptr->edev, EXTCON_USB_HOST)
>>> +                                       == true) {
>>> +                       instance_ptr->new_state = PHY2_DEV_HOST_CTRL_SEL_HOST;
>>> +                       cygnus_phy_dual_role_init(instance_ptr);
>>> +               }
>>> +       } else {
>>> +               dev_err(dev, "Extcon device handle not found\n");
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       return 0;
>>> +err:
>>> +       return ret;
>>> +}
>>> +
>>> +static const struct phy_ops ops = {
>>> +       .init           = cygnus_phy_init,
>>> +       .power_on       = cygnus_phy_poweron,
>>> +       .power_off      = cygnus_phy_shutdown,
>>> +       .owner = THIS_MODULE,
>>> +};
>>> +
>>> +static void cygnus_phy_shutdown_all(struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       u32 reg_val;
>>> +
>>> +       /*
>>> +        * Shutdown all ports. They can be powered up as
>>> +        * required
>>> +        */
>>> +       reg_val = readl(phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +                       CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +       reg_val &= ~CRMU_USBPHY_P0_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P0_RESETB;
>>> +       reg_val &= ~CRMU_USBPHY_P1_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P1_RESETB;
>>> +       reg_val &= ~CRMU_USBPHY_P2_AFE_CORERDY_VDDC;
>>> +       reg_val &= ~CRMU_USBPHY_P2_RESETB;
>>> +       writel(reg_val, phy_driver->crmu_usbphy_aon_ctrl_regs +
>>> +               CRMU_USB_PHY_AON_CTRL_OFFSET);
>>> +}
>>> +
>>> +static int cygnus_phy_instance_create(struct cygnus_phy_driver *phy_driver)
>>> +{
>>> +       struct device_node *child;
>>> +       char *vbus_name;
>>> +       struct platform_device *pdev = phy_driver->pdev;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct device_node *node = dev->of_node;
>>> +       struct cygnus_phy_instance *instance_ptr;
>>> +       unsigned int id, ret;
>>> +
>>> +       for_each_available_child_of_node(node, child) {
>>> +               if (of_property_read_u32(child, "reg", &id)) {
>>> +                       dev_err(dev, "missing reg property for %s\n",
>>> +                               child->name);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               if (id >= MAX_NUM_PHYS) {
>>> +                       dev_err(dev, "invalid PHY id: %u\n", id);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               instance_ptr = &phy_driver->instances[id];
>>> +               instance_ptr->driver = phy_driver;
>>> +               instance_ptr->port = id;
>>> +               spin_lock_init(&instance_ptr->lock);
>>> +
>>> +               if (instance_ptr->generic_phy) {
>>> +                       dev_err(dev, "duplicated PHY id: %u\n", id);
>>> +                       ret = -EINVAL;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               instance_ptr->generic_phy =
>>> +                               devm_phy_create(dev, child, &ops);
>>> +               if (IS_ERR(instance_ptr->generic_phy)) {
>>> +                       dev_err(dev, "Failed to create usb phy %d", id);
>>> +                       ret = PTR_ERR(instance_ptr->generic_phy);
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               vbus_name = devm_kzalloc(&instance_ptr->generic_phy->dev,
>>> +                                       MAX_REGULATOR_NAME_LEN,
>>> +                                       GFP_KERNEL);
>>> +               if (!vbus_name) {
>>> +                       ret = -ENOMEM;
>>> +                       goto put_child;
>>> +               }
>>> +
>>> +               /* regulator use is optional */
>>> +               sprintf(vbus_name, "vbus-p%d", id);
>>> +               instance_ptr->vbus_supply =
>>> +                       devm_regulator_get(&instance_ptr->generic_phy->dev,
>>> +                                       vbus_name);
>>> +               if (IS_ERR(instance_ptr->vbus_supply))
>>> +                       instance_ptr->vbus_supply = NULL;
>>> +               devm_kfree(&instance_ptr->generic_phy->dev, vbus_name);
>>> +               phy_set_drvdata(instance_ptr->generic_phy, instance_ptr);
>>> +       }
>>> +
>>> +       return 0;
>>> +
>>> +put_child:
>>> +       of_node_put(child);
>>> +       return ret;
>>> +}
>>> +
>>> +static int cygnus_phy_probe(struct platform_device *pdev)
>>> +{
>>> +       struct resource *res;
>>> +       struct cygnus_phy_driver *phy_driver;
>>> +       struct phy_provider *phy_provider;
>>> +       int i, ret;
>>> +       u32 reg_val;
>>> +       struct device *dev = &pdev->dev;
>>> +       struct device_node *node = dev->of_node;
>>> +
>>> +       /* allocate memory for each phy instance */
>>> +       phy_driver = devm_kzalloc(dev, sizeof(struct cygnus_phy_driver),
>>> +                                 GFP_KERNEL);
>>> +       if (!phy_driver)
>>> +               return -ENOMEM;
>>> +
>>> +       phy_driver->num_phys = of_get_child_count(node);
>>> +
>>> +       if (phy_driver->num_phys == 0) {
>>> +               dev_err(dev, "PHY no child node\n");
>>> +               return -ENODEV;
>>> +       }
>>> +
>>> +       phy_driver->instances = devm_kzalloc(dev, phy_driver->num_phys *
>>> +                                            sizeof(struct cygnus_phy_instance),
>>> +                                            GFP_KERNEL);
>>> +       if (!phy_driver->instances)
>>> +               return -ENOMEM;
>>> +
>>> +       phy_driver->pdev = pdev;
>>> +       platform_set_drvdata(pdev, phy_driver);
>>> +
>>> +       ret = cygnus_phy_instance_create(phy_driver);
>>> +       if (ret)
>>> +               return ret;
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +                                          "crmu-usbphy-aon-ctrl");
>>> +       phy_driver->crmu_usbphy_aon_ctrl_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs))
>>> +               return PTR_ERR(phy_driver->crmu_usbphy_aon_ctrl_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +                                          "cdru-usbphy");
>>> +       phy_driver->cdru_usbphy_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->cdru_usbphy_regs))
>>> +               return PTR_ERR(phy_driver->cdru_usbphy_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2h-idm");
>>> +       phy_driver->usb2h_idm_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->usb2h_idm_regs))
>>> +               return PTR_ERR(phy_driver->usb2h_idm_regs);
>>> +
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2d-idm");
>>> +       phy_driver->usb2d_idm_regs = devm_ioremap_resource(dev, res);
>>> +       if (IS_ERR(phy_driver->usb2d_idm_regs))
>>> +               return PTR_ERR(phy_driver->usb2d_idm_regs);
>>> +
>>> +       reg_val = readl(phy_driver->usb2d_idm_regs);
>>> +       writel((reg_val | USB2D_IDM_IDM_IO_SS_CLEAR_NAK_NEMPTY_EN_I),
>>> +                  phy_driver->usb2d_idm_regs);
>>> +       phy_driver->idm_host_enabled = false;
>>> +
>>> +       /* Shutdown all ports. They can be powered up as required */
>>> +       cygnus_phy_shutdown_all(phy_driver);
>>> +
>>> +       phy_provider = devm_of_phy_provider_register(dev, cygnus_phy_xlate);
>>> +       if (IS_ERR(phy_provider)) {
>>> +               dev_err(dev, "Failed to register as phy provider\n");
>>> +               ret = PTR_ERR(phy_provider);
>>> +               return ret;
>>> +       }
>>> +
>>> +       for (i = 0; i < phy_driver->num_phys; i++) {
>>> +               if (phy_driver->instances[i].port == DUAL_ROLE_PHY) {
>>> +                       ret = cygnus_register_extcon_notifiers(
>>> +                               &phy_driver->instances[DUAL_ROLE_PHY]);
>>> +                       if (ret) {
>>> +                               dev_err(dev, "Failed to register notifier\n");
>>> +                               return ret;
>>> +                       }
>>> +               }
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static const struct of_device_id cygnus_phy_dt_ids[] = {
>>> +       { .compatible = "brcm,cygnus-usb-phy", },
>>> +       { }
>>> +};
>>> +MODULE_DEVICE_TABLE(of, cygnus_phy_dt_ids);
>>> +
>>> +static struct platform_driver cygnus_phy_driver = {
>>> +       .probe = cygnus_phy_probe,
>>> +       .driver = {
>>> +               .name = "bcm-cygnus-usbphy",
>>> +               .of_match_table = of_match_ptr(cygnus_phy_dt_ids),
>>> +       },
>>> +};
>>> +module_platform_driver(cygnus_phy_driver);
>>> +
>>> +MODULE_ALIAS("platform:bcm-cygnus-usbphy");
>>> +MODULE_AUTHOR("Raveendra Padasalagi <Raveendra.padasalagi@broadcom.com");
>>> +MODULE_DESCRIPTION("Broadcom Cygnus USB PHY driver");
>>> +MODULE_LICENSE("GPL v2");
>>> --
>>> 1.9.1
>>>
>>
>>
>>
>
>
> --
> Best Regards,
> Chanwoo Choi
> Samsung Electronics

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
  2017-11-08  7:46   ` Raveendra Padasalagi
@ 2017-11-10 21:44     ` Rob Herring
  -1 siblings, 0 replies; 29+ messages in thread
From: Rob Herring @ 2017-11-10 21:44 UTC (permalink / raw)
  To: Raveendra Padasalagi
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list

On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
> Add devicetree binding document for broadcom's
> Cygnus SoC specific usb phy controller driver.
> 
> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
> ---
>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>  1 file changed, 106 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
> 
> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
> new file mode 100644
> index 0000000..bbc4b94
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
> @@ -0,0 +1,106 @@
> +BROADCOM CYGNUS USB PHY
> +
> +Required Properties:
> +- compatible:  brcm,cygnus-usb-phy
> +- reg : the register start address and length for
> +        crmu_usbphy_aon_ctrl,
> +        cdru usb phy control,
> +        usb host idm registers,
> +        usb device idm registers.
> +- reg-names: a list of the names corresponding to the previous register ranges
> +  Should contain
> +        "crmu-usbphy-aon-ctrl",
> +        "cdru-usbphy",
> +        "usb2h-idm",
> +        "usb2d-idm".
> +- address-cells: should be 1
> +- size-cells: should be 0
> +
> +Sub-nodes:
> +  Each port's PHY should be represented as a sub-node.
> +
> +Sub-nodes required properties:
> +- reg: the PHY number
> +- #phy-cells must be 1
> +  The node that uses the phy must provide 1 integer argument specifying
> +  port number.
> +
> +Optional Properties:
> +- vbus-p#-supply : The regulator for vbus out control for the host

Is this a literal # or something else?

> +  functionality enabled ports.
> +- vbus-gpios: vbus gpio binding
> +  This is mandatory for port 2, as port 2 is used as dual role phy.
> +  Based on the vbus and id values device or host role is determined
> +  for phy 2.

These optional properties don't match with the example.

> +
> +- extcon: extcon phandle
> +  This is mandatory for port 2,  as port 2 is used as dual role phy.
> +  extcon should be phandle to external usb gpio module which provide
> +  device or host role notifications based on the ID and VBUS gpio's state.
> +
> +
> +Refer to phy/phy-bindings.txt for the generic PHY binding properties
> +
> +NOTE: port 0 and port 1 are host only and port 2 is dual role port.
> +
> +Example of phy :
> +	usbphy: usb-phy@0301c028 {
> +		compatible = "brcm,cygnus-usb-phy";
> +		reg = <0x0301c028 0x4>,
> +		      <0x0301d1b4 0x5c>,
> +		      <0x18115000 0xa00>,
> +		      <0x18111000 0xa00>;
> +		reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
> +			    "usb2h-idm", "usb2d-idm";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		usbphy0: usb-phy@0 {
> +			reg = <0>;
> +			#phy-cells = <1>;
> +		};
> +
> +		usbphy1: usb-phy@1 {
> +			reg = <1>;
> +			#phy-cells = <1>;
> +		};
> +
> +		usbphy2: usb-phy@2 {
> +			reg = <2>;
> +			#phy-cells = <1>;
> +			extcon = <&extcon_usb>;
> +		};
> +	};
> +
> +	extcon_usb: extcon_usb {
> +		compatible = "linux,extcon-usb-gpio";
> +		vbus-gpio = <&gpio_asiu 121 0>;
> +		id-gpio = <&gpio_asiu 122 0>;
> +		status = "okay";
> +	};
> +
> +
> +Example of node using the phy:
> +
> +	/* This nodes declares all three ports, port 0
> +	and port 1 are host and port 2 is device or host */
> +
> +	ehci0: usb@18048000 {
> +		compatible = "generic-ehci";
> +		reg = <0x18048000 0x100>;
> +		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
> +		phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
> +		phy-names = "usbp0","usbp1","usbp2";
> +		status = "okay";

Don't show status in examples.

> +	};
> +
> +	/* This node declares port 2 phy
> +	and configures it for device */
> +
> +	usbd_udc_dwc1: usb@1804c000 {
> +		compatible = "iproc-udc";
> +		reg = <0x1804c000 0x2000>;
> +		interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
> +		phys = <&usbphy2 2>;
> +		phy-names = "usbdrd";
> +	};
> -- 
> 1.9.1
> 

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-10 21:44     ` Rob Herring
  0 siblings, 0 replies; 29+ messages in thread
From: Rob Herring @ 2017-11-10 21:44 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
> Add devicetree binding document for broadcom's
> Cygnus SoC specific usb phy controller driver.
> 
> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
> ---
>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>  1 file changed, 106 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
> 
> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
> new file mode 100644
> index 0000000..bbc4b94
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
> @@ -0,0 +1,106 @@
> +BROADCOM CYGNUS USB PHY
> +
> +Required Properties:
> +- compatible:  brcm,cygnus-usb-phy
> +- reg : the register start address and length for
> +        crmu_usbphy_aon_ctrl,
> +        cdru usb phy control,
> +        usb host idm registers,
> +        usb device idm registers.
> +- reg-names: a list of the names corresponding to the previous register ranges
> +  Should contain
> +        "crmu-usbphy-aon-ctrl",
> +        "cdru-usbphy",
> +        "usb2h-idm",
> +        "usb2d-idm".
> +- address-cells: should be 1
> +- size-cells: should be 0
> +
> +Sub-nodes:
> +  Each port's PHY should be represented as a sub-node.
> +
> +Sub-nodes required properties:
> +- reg: the PHY number
> +- #phy-cells must be 1
> +  The node that uses the phy must provide 1 integer argument specifying
> +  port number.
> +
> +Optional Properties:
> +- vbus-p#-supply : The regulator for vbus out control for the host

Is this a literal # or something else?

> +  functionality enabled ports.
> +- vbus-gpios: vbus gpio binding
> +  This is mandatory for port 2, as port 2 is used as dual role phy.
> +  Based on the vbus and id values device or host role is determined
> +  for phy 2.

These optional properties don't match with the example.

> +
> +- extcon: extcon phandle
> +  This is mandatory for port 2,  as port 2 is used as dual role phy.
> +  extcon should be phandle to external usb gpio module which provide
> +  device or host role notifications based on the ID and VBUS gpio's state.
> +
> +
> +Refer to phy/phy-bindings.txt for the generic PHY binding properties
> +
> +NOTE: port 0 and port 1 are host only and port 2 is dual role port.
> +
> +Example of phy :
> +	usbphy: usb-phy at 0301c028 {
> +		compatible = "brcm,cygnus-usb-phy";
> +		reg = <0x0301c028 0x4>,
> +		      <0x0301d1b4 0x5c>,
> +		      <0x18115000 0xa00>,
> +		      <0x18111000 0xa00>;
> +		reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
> +			    "usb2h-idm", "usb2d-idm";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		usbphy0: usb-phy at 0 {
> +			reg = <0>;
> +			#phy-cells = <1>;
> +		};
> +
> +		usbphy1: usb-phy at 1 {
> +			reg = <1>;
> +			#phy-cells = <1>;
> +		};
> +
> +		usbphy2: usb-phy at 2 {
> +			reg = <2>;
> +			#phy-cells = <1>;
> +			extcon = <&extcon_usb>;
> +		};
> +	};
> +
> +	extcon_usb: extcon_usb {
> +		compatible = "linux,extcon-usb-gpio";
> +		vbus-gpio = <&gpio_asiu 121 0>;
> +		id-gpio = <&gpio_asiu 122 0>;
> +		status = "okay";
> +	};
> +
> +
> +Example of node using the phy:
> +
> +	/* This nodes declares all three ports, port 0
> +	and port 1 are host and port 2 is device or host */
> +
> +	ehci0: usb at 18048000 {
> +		compatible = "generic-ehci";
> +		reg = <0x18048000 0x100>;
> +		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
> +		phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
> +		phy-names = "usbp0","usbp1","usbp2";
> +		status = "okay";

Don't show status in examples.

> +	};
> +
> +	/* This node declares port 2 phy
> +	and configures it for device */
> +
> +	usbd_udc_dwc1: usb at 1804c000 {
> +		compatible = "iproc-udc";
> +		reg = <0x1804c000 0x2000>;
> +		interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
> +		phys = <&usbphy2 2>;
> +		phy-names = "usbdrd";
> +	};
> -- 
> 1.9.1
> 

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
  2017-11-10 21:44     ` Rob Herring
  (?)
@ 2017-11-13  4:23       ` Raveendra Padasalagi
  -1 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-13  4:23 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list

Hi,

On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh@kernel.org> wrote:
> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>> Add devicetree binding document for broadcom's
>> Cygnus SoC specific usb phy controller driver.
>>
>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>> ---
>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>  1 file changed, 106 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>
>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>> new file mode 100644
>> index 0000000..bbc4b94
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>> @@ -0,0 +1,106 @@
>> +BROADCOM CYGNUS USB PHY
>> +
>> +Required Properties:
>> +- compatible:  brcm,cygnus-usb-phy
>> +- reg : the register start address and length for
>> +        crmu_usbphy_aon_ctrl,
>> +        cdru usb phy control,
>> +        usb host idm registers,
>> +        usb device idm registers.
>> +- reg-names: a list of the names corresponding to the previous register ranges
>> +  Should contain
>> +        "crmu-usbphy-aon-ctrl",
>> +        "cdru-usbphy",
>> +        "usb2h-idm",
>> +        "usb2d-idm".
>> +- address-cells: should be 1
>> +- size-cells: should be 0
>> +
>> +Sub-nodes:
>> +  Each port's PHY should be represented as a sub-node.
>> +
>> +Sub-nodes required properties:
>> +- reg: the PHY number
>> +- #phy-cells must be 1
>> +  The node that uses the phy must provide 1 integer argument specifying
>> +  port number.
>> +
>> +Optional Properties:
>> +- vbus-p#-supply : The regulator for vbus out control for the host
>
> Is this a literal # or something else?

Yes, this is a literal. It's assumed # will replace numeric 0-2 for
each of the ports.
In the example it's not shown as the regulators specified in vbus-p#-supply
are board specific.

>> +  functionality enabled ports.
>> +- vbus-gpios: vbus gpio binding
>> +  This is mandatory for port 2, as port 2 is used as dual role phy.
>> +  Based on the vbus and id values device or host role is determined
>> +  for phy 2.
>
> These optional properties don't match with the example.
vbus-gpios is placed by mistake here in the documentation,
this is no more required. I will remove it in the next version of the patch.

>> +
>> +- extcon: extcon phandle
>> +  This is mandatory for port 2,  as port 2 is used as dual role phy.
>> +  extcon should be phandle to external usb gpio module which provide
>> +  device or host role notifications based on the ID and VBUS gpio's state.
>> +
>> +
>> +Refer to phy/phy-bindings.txt for the generic PHY binding properties
>> +
>> +NOTE: port 0 and port 1 are host only and port 2 is dual role port.
>> +
>> +Example of phy :
>> +     usbphy: usb-phy@0301c028 {
>> +             compatible = "brcm,cygnus-usb-phy";
>> +             reg = <0x0301c028 0x4>,
>> +                   <0x0301d1b4 0x5c>,
>> +                   <0x18115000 0xa00>,
>> +                   <0x18111000 0xa00>;
>> +             reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
>> +                         "usb2h-idm", "usb2d-idm";
>> +             #address-cells = <1>;
>> +             #size-cells = <0>;
>> +
>> +             usbphy0: usb-phy@0 {
>> +                     reg = <0>;
>> +                     #phy-cells = <1>;
>> +             };
>> +
>> +             usbphy1: usb-phy@1 {
>> +                     reg = <1>;
>> +                     #phy-cells = <1>;
>> +             };
>> +
>> +             usbphy2: usb-phy@2 {
>> +                     reg = <2>;
>> +                     #phy-cells = <1>;
>> +                     extcon = <&extcon_usb>;
>> +             };
>> +     };
>> +
>> +     extcon_usb: extcon_usb {
>> +             compatible = "linux,extcon-usb-gpio";
>> +             vbus-gpio = <&gpio_asiu 121 0>;
>> +             id-gpio = <&gpio_asiu 122 0>;
>> +             status = "okay";
>> +     };
>> +
>> +
>> +Example of node using the phy:
>> +
>> +     /* This nodes declares all three ports, port 0
>> +     and port 1 are host and port 2 is device or host */
>> +
>> +     ehci0: usb@18048000 {
>> +             compatible = "generic-ehci";
>> +             reg = <0x18048000 0x100>;
>> +             interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
>> +             phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
>> +             phy-names = "usbp0","usbp1","usbp2";
>> +             status = "okay";
>
> Don't show status in examples.

Ok, Thanks. I will update it in the next version of the patch.

>> +     };
>> +
>> +     /* This node declares port 2 phy
>> +     and configures it for device */
>> +
>> +     usbd_udc_dwc1: usb@1804c000 {
>> +             compatible = "iproc-udc";
>> +             reg = <0x1804c000 0x2000>;
>> +             interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
>> +             phys = <&usbphy2 2>;
>> +             phy-names = "usbdrd";
>> +     };
>> --
>> 1.9.1
>>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-13  4:23       ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-13  4:23 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, bcm-kernel-feedback-list

Hi,

On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>> Add devicetree binding document for broadcom's
>> Cygnus SoC specific usb phy controller driver.
>>
>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
>> ---
>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>  1 file changed, 106 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>
>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>> new file mode 100644
>> index 0000000..bbc4b94
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>> @@ -0,0 +1,106 @@
>> +BROADCOM CYGNUS USB PHY
>> +
>> +Required Properties:
>> +- compatible:  brcm,cygnus-usb-phy
>> +- reg : the register start address and length for
>> +        crmu_usbphy_aon_ctrl,
>> +        cdru usb phy control,
>> +        usb host idm registers,
>> +        usb device idm registers.
>> +- reg-names: a list of the names corresponding to the previous register ranges
>> +  Should contain
>> +        "crmu-usbphy-aon-ctrl",
>> +        "cdru-usbphy",
>> +        "usb2h-idm",
>> +        "usb2d-idm".
>> +- address-cells: should be 1
>> +- size-cells: should be 0
>> +
>> +Sub-nodes:
>> +  Each port's PHY should be represented as a sub-node.
>> +
>> +Sub-nodes required properties:
>> +- reg: the PHY number
>> +- #phy-cells must be 1
>> +  The node that uses the phy must provide 1 integer argument specifying
>> +  port number.
>> +
>> +Optional Properties:
>> +- vbus-p#-supply : The regulator for vbus out control for the host
>
> Is this a literal # or something else?

Yes, this is a literal. It's assumed # will replace numeric 0-2 for
each of the ports.
In the example it's not shown as the regulators specified in vbus-p#-supply
are board specific.

>> +  functionality enabled ports.
>> +- vbus-gpios: vbus gpio binding
>> +  This is mandatory for port 2, as port 2 is used as dual role phy.
>> +  Based on the vbus and id values device or host role is determined
>> +  for phy 2.
>
> These optional properties don't match with the example.
vbus-gpios is placed by mistake here in the documentation,
this is no more required. I will remove it in the next version of the patch.

>> +
>> +- extcon: extcon phandle
>> +  This is mandatory for port 2,  as port 2 is used as dual role phy.
>> +  extcon should be phandle to external usb gpio module which provide
>> +  device or host role notifications based on the ID and VBUS gpio's state.
>> +
>> +
>> +Refer to phy/phy-bindings.txt for the generic PHY binding properties
>> +
>> +NOTE: port 0 and port 1 are host only and port 2 is dual role port.
>> +
>> +Example of phy :
>> +     usbphy: usb-phy@0301c028 {
>> +             compatible = "brcm,cygnus-usb-phy";
>> +             reg = <0x0301c028 0x4>,
>> +                   <0x0301d1b4 0x5c>,
>> +                   <0x18115000 0xa00>,
>> +                   <0x18111000 0xa00>;
>> +             reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
>> +                         "usb2h-idm", "usb2d-idm";
>> +             #address-cells = <1>;
>> +             #size-cells = <0>;
>> +
>> +             usbphy0: usb-phy@0 {
>> +                     reg = <0>;
>> +                     #phy-cells = <1>;
>> +             };
>> +
>> +             usbphy1: usb-phy@1 {
>> +                     reg = <1>;
>> +                     #phy-cells = <1>;
>> +             };
>> +
>> +             usbphy2: usb-phy@2 {
>> +                     reg = <2>;
>> +                     #phy-cells = <1>;
>> +                     extcon = <&extcon_usb>;
>> +             };
>> +     };
>> +
>> +     extcon_usb: extcon_usb {
>> +             compatible = "linux,extcon-usb-gpio";
>> +             vbus-gpio = <&gpio_asiu 121 0>;
>> +             id-gpio = <&gpio_asiu 122 0>;
>> +             status = "okay";
>> +     };
>> +
>> +
>> +Example of node using the phy:
>> +
>> +     /* This nodes declares all three ports, port 0
>> +     and port 1 are host and port 2 is device or host */
>> +
>> +     ehci0: usb@18048000 {
>> +             compatible = "generic-ehci";
>> +             reg = <0x18048000 0x100>;
>> +             interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
>> +             phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
>> +             phy-names = "usbp0","usbp1","usbp2";
>> +             status = "okay";
>
> Don't show status in examples.

Ok, Thanks. I will update it in the next version of the patch.

>> +     };
>> +
>> +     /* This node declares port 2 phy
>> +     and configures it for device */
>> +
>> +     usbd_udc_dwc1: usb@1804c000 {
>> +             compatible = "iproc-udc";
>> +             reg = <0x1804c000 0x2000>;
>> +             interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
>> +             phys = <&usbphy2 2>;
>> +             phy-names = "usbdrd";
>> +     };
>> --
>> 1.9.1
>>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-13  4:23       ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-13  4:23 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh@kernel.org> wrote:
> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>> Add devicetree binding document for broadcom's
>> Cygnus SoC specific usb phy controller driver.
>>
>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>> ---
>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>  1 file changed, 106 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>
>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>> new file mode 100644
>> index 0000000..bbc4b94
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>> @@ -0,0 +1,106 @@
>> +BROADCOM CYGNUS USB PHY
>> +
>> +Required Properties:
>> +- compatible:  brcm,cygnus-usb-phy
>> +- reg : the register start address and length for
>> +        crmu_usbphy_aon_ctrl,
>> +        cdru usb phy control,
>> +        usb host idm registers,
>> +        usb device idm registers.
>> +- reg-names: a list of the names corresponding to the previous register ranges
>> +  Should contain
>> +        "crmu-usbphy-aon-ctrl",
>> +        "cdru-usbphy",
>> +        "usb2h-idm",
>> +        "usb2d-idm".
>> +- address-cells: should be 1
>> +- size-cells: should be 0
>> +
>> +Sub-nodes:
>> +  Each port's PHY should be represented as a sub-node.
>> +
>> +Sub-nodes required properties:
>> +- reg: the PHY number
>> +- #phy-cells must be 1
>> +  The node that uses the phy must provide 1 integer argument specifying
>> +  port number.
>> +
>> +Optional Properties:
>> +- vbus-p#-supply : The regulator for vbus out control for the host
>
> Is this a literal # or something else?

Yes, this is a literal. It's assumed # will replace numeric 0-2 for
each of the ports.
In the example it's not shown as the regulators specified in vbus-p#-supply
are board specific.

>> +  functionality enabled ports.
>> +- vbus-gpios: vbus gpio binding
>> +  This is mandatory for port 2, as port 2 is used as dual role phy.
>> +  Based on the vbus and id values device or host role is determined
>> +  for phy 2.
>
> These optional properties don't match with the example.
vbus-gpios is placed by mistake here in the documentation,
this is no more required. I will remove it in the next version of the patch.

>> +
>> +- extcon: extcon phandle
>> +  This is mandatory for port 2,  as port 2 is used as dual role phy.
>> +  extcon should be phandle to external usb gpio module which provide
>> +  device or host role notifications based on the ID and VBUS gpio's state.
>> +
>> +
>> +Refer to phy/phy-bindings.txt for the generic PHY binding properties
>> +
>> +NOTE: port 0 and port 1 are host only and port 2 is dual role port.
>> +
>> +Example of phy :
>> +     usbphy: usb-phy at 0301c028 {
>> +             compatible = "brcm,cygnus-usb-phy";
>> +             reg = <0x0301c028 0x4>,
>> +                   <0x0301d1b4 0x5c>,
>> +                   <0x18115000 0xa00>,
>> +                   <0x18111000 0xa00>;
>> +             reg-names = "crmu-usbphy-aon-ctrl", "cdru-usbphy",
>> +                         "usb2h-idm", "usb2d-idm";
>> +             #address-cells = <1>;
>> +             #size-cells = <0>;
>> +
>> +             usbphy0: usb-phy at 0 {
>> +                     reg = <0>;
>> +                     #phy-cells = <1>;
>> +             };
>> +
>> +             usbphy1: usb-phy at 1 {
>> +                     reg = <1>;
>> +                     #phy-cells = <1>;
>> +             };
>> +
>> +             usbphy2: usb-phy at 2 {
>> +                     reg = <2>;
>> +                     #phy-cells = <1>;
>> +                     extcon = <&extcon_usb>;
>> +             };
>> +     };
>> +
>> +     extcon_usb: extcon_usb {
>> +             compatible = "linux,extcon-usb-gpio";
>> +             vbus-gpio = <&gpio_asiu 121 0>;
>> +             id-gpio = <&gpio_asiu 122 0>;
>> +             status = "okay";
>> +     };
>> +
>> +
>> +Example of node using the phy:
>> +
>> +     /* This nodes declares all three ports, port 0
>> +     and port 1 are host and port 2 is device or host */
>> +
>> +     ehci0: usb at 18048000 {
>> +             compatible = "generic-ehci";
>> +             reg = <0x18048000 0x100>;
>> +             interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
>> +             phys = <&usbphy0 0 &usbphy1 1 &usbphy2 2>;
>> +             phy-names = "usbp0","usbp1","usbp2";
>> +             status = "okay";
>
> Don't show status in examples.

Ok, Thanks. I will update it in the next version of the patch.

>> +     };
>> +
>> +     /* This node declares port 2 phy
>> +     and configures it for device */
>> +
>> +     usbd_udc_dwc1: usb at 1804c000 {
>> +             compatible = "iproc-udc";
>> +             reg = <0x1804c000 0x2000>;
>> +             interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
>> +             phys = <&usbphy2 2>;
>> +             phy-names = "usbdrd";
>> +     };
>> --
>> 1.9.1
>>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-13 17:53         ` Rob Herring
  0 siblings, 0 replies; 29+ messages in thread
From: Rob Herring @ 2017-11-13 17:53 UTC (permalink / raw)
  To: Raveendra Padasalagi
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list

On Sun, Nov 12, 2017 at 10:23 PM, Raveendra Padasalagi
<raveendra.padasalagi@broadcom.com> wrote:
> Hi,
>
> On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh@kernel.org> wrote:
>> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>>> Add devicetree binding document for broadcom's
>>> Cygnus SoC specific usb phy controller driver.
>>>
>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>> ---
>>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>>  1 file changed, 106 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>
>>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>> new file mode 100644
>>> index 0000000..bbc4b94
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>> @@ -0,0 +1,106 @@
>>> +BROADCOM CYGNUS USB PHY
>>> +
>>> +Required Properties:
>>> +- compatible:  brcm,cygnus-usb-phy
>>> +- reg : the register start address and length for
>>> +        crmu_usbphy_aon_ctrl,
>>> +        cdru usb phy control,
>>> +        usb host idm registers,
>>> +        usb device idm registers.
>>> +- reg-names: a list of the names corresponding to the previous register ranges
>>> +  Should contain
>>> +        "crmu-usbphy-aon-ctrl",
>>> +        "cdru-usbphy",
>>> +        "usb2h-idm",
>>> +        "usb2d-idm".
>>> +- address-cells: should be 1
>>> +- size-cells: should be 0
>>> +
>>> +Sub-nodes:
>>> +  Each port's PHY should be represented as a sub-node.
>>> +
>>> +Sub-nodes required properties:
>>> +- reg: the PHY number
>>> +- #phy-cells must be 1
>>> +  The node that uses the phy must provide 1 integer argument specifying
>>> +  port number.
>>> +
>>> +Optional Properties:
>>> +- vbus-p#-supply : The regulator for vbus out control for the host
>>
>> Is this a literal # or something else?
>
> Yes, this is a literal. It's assumed # will replace numeric 0-2 for
> each of the ports.

I'm still confused. Which is valid? "vbus-p#-supply" or "vbus-p0-supply"

If the latter, you need to enumerate all valid options. But these are
in sub nodes for each port, so just "vbus-supply" should be
sufficient.

One more question, does Vbus actually supply power to the phy or you
are just associating the Vbus supply to a connector with a port? The
latter needs a connector node instead and Vbus should be part of that.
There's been some attempts at USB connectors, but we don't really have
one yet (the extcon binding is not it).

> In the example it's not shown as the regulators specified in vbus-p#-supply
> are board specific.

Please show in the example. Examples should be complete.

Rob

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-13 17:53         ` Rob Herring
  0 siblings, 0 replies; 29+ messages in thread
From: Rob Herring @ 2017-11-13 17:53 UTC (permalink / raw)
  To: Raveendra Padasalagi
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-ke

On Sun, Nov 12, 2017 at 10:23 PM, Raveendra Padasalagi
<raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
> Hi,
>
> On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
>> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>>> Add devicetree binding document for broadcom's
>>> Cygnus SoC specific usb phy controller driver.
>>>
>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
>>> ---
>>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>>  1 file changed, 106 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>
>>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>> new file mode 100644
>>> index 0000000..bbc4b94
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>> @@ -0,0 +1,106 @@
>>> +BROADCOM CYGNUS USB PHY
>>> +
>>> +Required Properties:
>>> +- compatible:  brcm,cygnus-usb-phy
>>> +- reg : the register start address and length for
>>> +        crmu_usbphy_aon_ctrl,
>>> +        cdru usb phy control,
>>> +        usb host idm registers,
>>> +        usb device idm registers.
>>> +- reg-names: a list of the names corresponding to the previous register ranges
>>> +  Should contain
>>> +        "crmu-usbphy-aon-ctrl",
>>> +        "cdru-usbphy",
>>> +        "usb2h-idm",
>>> +        "usb2d-idm".
>>> +- address-cells: should be 1
>>> +- size-cells: should be 0
>>> +
>>> +Sub-nodes:
>>> +  Each port's PHY should be represented as a sub-node.
>>> +
>>> +Sub-nodes required properties:
>>> +- reg: the PHY number
>>> +- #phy-cells must be 1
>>> +  The node that uses the phy must provide 1 integer argument specifying
>>> +  port number.
>>> +
>>> +Optional Properties:
>>> +- vbus-p#-supply : The regulator for vbus out control for the host
>>
>> Is this a literal # or something else?
>
> Yes, this is a literal. It's assumed # will replace numeric 0-2 for
> each of the ports.

I'm still confused. Which is valid? "vbus-p#-supply" or "vbus-p0-supply"

If the latter, you need to enumerate all valid options. But these are
in sub nodes for each port, so just "vbus-supply" should be
sufficient.

One more question, does Vbus actually supply power to the phy or you
are just associating the Vbus supply to a connector with a port? The
latter needs a connector node instead and Vbus should be part of that.
There's been some attempts at USB connectors, but we don't really have
one yet (the extcon binding is not it).

> In the example it's not shown as the regulators specified in vbus-p#-supply
> are board specific.

Please show in the example. Examples should be complete.

Rob
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-13 17:53         ` Rob Herring
  0 siblings, 0 replies; 29+ messages in thread
From: Rob Herring @ 2017-11-13 17:53 UTC (permalink / raw)
  To: linux-arm-kernel

On Sun, Nov 12, 2017 at 10:23 PM, Raveendra Padasalagi
<raveendra.padasalagi@broadcom.com> wrote:
> Hi,
>
> On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh@kernel.org> wrote:
>> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>>> Add devicetree binding document for broadcom's
>>> Cygnus SoC specific usb phy controller driver.
>>>
>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>> ---
>>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>>  1 file changed, 106 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>
>>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>> new file mode 100644
>>> index 0000000..bbc4b94
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>> @@ -0,0 +1,106 @@
>>> +BROADCOM CYGNUS USB PHY
>>> +
>>> +Required Properties:
>>> +- compatible:  brcm,cygnus-usb-phy
>>> +- reg : the register start address and length for
>>> +        crmu_usbphy_aon_ctrl,
>>> +        cdru usb phy control,
>>> +        usb host idm registers,
>>> +        usb device idm registers.
>>> +- reg-names: a list of the names corresponding to the previous register ranges
>>> +  Should contain
>>> +        "crmu-usbphy-aon-ctrl",
>>> +        "cdru-usbphy",
>>> +        "usb2h-idm",
>>> +        "usb2d-idm".
>>> +- address-cells: should be 1
>>> +- size-cells: should be 0
>>> +
>>> +Sub-nodes:
>>> +  Each port's PHY should be represented as a sub-node.
>>> +
>>> +Sub-nodes required properties:
>>> +- reg: the PHY number
>>> +- #phy-cells must be 1
>>> +  The node that uses the phy must provide 1 integer argument specifying
>>> +  port number.
>>> +
>>> +Optional Properties:
>>> +- vbus-p#-supply : The regulator for vbus out control for the host
>>
>> Is this a literal # or something else?
>
> Yes, this is a literal. It's assumed # will replace numeric 0-2 for
> each of the ports.

I'm still confused. Which is valid? "vbus-p#-supply" or "vbus-p0-supply"

If the latter, you need to enumerate all valid options. But these are
in sub nodes for each port, so just "vbus-supply" should be
sufficient.

One more question, does Vbus actually supply power to the phy or you
are just associating the Vbus supply to a connector with a port? The
latter needs a connector node instead and Vbus should be part of that.
There's been some attempts at USB connectors, but we don't really have
one yet (the extcon binding is not it).

> In the example it's not shown as the regulators specified in vbus-p#-supply
> are board specific.

Please show in the example. Examples should be complete.

Rob

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-14  5:29           ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-14  5:29 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree, linux-arm-kernel, linux-kernel,
	bcm-kernel-feedback-list

Hi Rob,

On Mon, Nov 13, 2017 at 11:23 PM, Rob Herring <robh@kernel.org> wrote:
> On Sun, Nov 12, 2017 at 10:23 PM, Raveendra Padasalagi
> <raveendra.padasalagi@broadcom.com> wrote:
>> Hi,
>>
>> On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh@kernel.org> wrote:
>>> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>>>> Add devicetree binding document for broadcom's
>>>> Cygnus SoC specific usb phy controller driver.
>>>>
>>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>>> ---
>>>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>>>  1 file changed, 106 insertions(+)
>>>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>> new file mode 100644
>>>> index 0000000..bbc4b94
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>> @@ -0,0 +1,106 @@
>>>> +BROADCOM CYGNUS USB PHY
>>>> +
>>>> +Required Properties:
>>>> +- compatible:  brcm,cygnus-usb-phy
>>>> +- reg : the register start address and length for
>>>> +        crmu_usbphy_aon_ctrl,
>>>> +        cdru usb phy control,
>>>> +        usb host idm registers,
>>>> +        usb device idm registers.
>>>> +- reg-names: a list of the names corresponding to the previous register ranges
>>>> +  Should contain
>>>> +        "crmu-usbphy-aon-ctrl",
>>>> +        "cdru-usbphy",
>>>> +        "usb2h-idm",
>>>> +        "usb2d-idm".
>>>> +- address-cells: should be 1
>>>> +- size-cells: should be 0
>>>> +
>>>> +Sub-nodes:
>>>> +  Each port's PHY should be represented as a sub-node.
>>>> +
>>>> +Sub-nodes required properties:
>>>> +- reg: the PHY number
>>>> +- #phy-cells must be 1
>>>> +  The node that uses the phy must provide 1 integer argument specifying
>>>> +  port number.
>>>> +
>>>> +Optional Properties:
>>>> +- vbus-p#-supply : The regulator for vbus out control for the host
>>>
>>> Is this a literal # or something else?
>>
>> Yes, this is a literal. It's assumed # will replace numeric 0-2 for
>> each of the ports.
>
> I'm still confused. Which is valid? "vbus-p#-supply" or "vbus-p0-supply"
>
I agree, it's creating confusion. Instead of enumerating
"vbus-p0-supply", "vbus-p1-supply", "vbus-p2-supply" kept "vbus-p#-supply".

Yes, as suggested by you "vbus-supply" should be sufficient as it's in each
of phy sub node.

> If the latter, you need to enumerate all valid options. But these are
> in sub nodes for each port, so just "vbus-supply" should be
> sufficient.

Keeping "vbus-supply" should not create any confusion. Will send out the
change in next version of the patch.

> One more question, does Vbus actually supply power to the phy or you
> are just associating the Vbus supply to a connector with a port? The
> latter needs a connector node instead and Vbus should be part of that.
> There's been some attempts at USB connectors, but we don't really have
> one yet (the extcon binding is not it).

Vbus is not supplied to phy, it's been given to the devices connected on
the port and in our platform vbus is controlled through an external regulator
which is controlled through gpio.
So "vbus-supply" shown above actually points to the phandle of vbus regulator
node.

>> In the example it's not shown as the regulators specified in vbus-p#-supply
>> are board specific.
>
> Please show in the example. Examples should be complete.

Ok. Sure.

> Rob

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-14  5:29           ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-14  5:29 UTC (permalink / raw)
  To: Rob Herring
  Cc: Mark Rutland, Kishon Vijay Abraham I, Russell King,
	Scott Branden, Ray Jui, Srinath Mannam, Vikram Prakash,
	Jon Mason, Florian Fainelli, Yoshihiro Shimoda,
	Raviteja Garimella, Rafal Milecki, Arnd Bergmann, Viresh Kumar,
	Jaehoon Chung, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, linux-ke

Hi Rob,

On Mon, Nov 13, 2017 at 11:23 PM, Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Sun, Nov 12, 2017 at 10:23 PM, Raveendra Padasalagi
> <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
>> Hi,
>>
>> On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
>>> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>>>> Add devicetree binding document for broadcom's
>>>> Cygnus SoC specific usb phy controller driver.
>>>>
>>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
>>>> ---
>>>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>>>  1 file changed, 106 insertions(+)
>>>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>> new file mode 100644
>>>> index 0000000..bbc4b94
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>> @@ -0,0 +1,106 @@
>>>> +BROADCOM CYGNUS USB PHY
>>>> +
>>>> +Required Properties:
>>>> +- compatible:  brcm,cygnus-usb-phy
>>>> +- reg : the register start address and length for
>>>> +        crmu_usbphy_aon_ctrl,
>>>> +        cdru usb phy control,
>>>> +        usb host idm registers,
>>>> +        usb device idm registers.
>>>> +- reg-names: a list of the names corresponding to the previous register ranges
>>>> +  Should contain
>>>> +        "crmu-usbphy-aon-ctrl",
>>>> +        "cdru-usbphy",
>>>> +        "usb2h-idm",
>>>> +        "usb2d-idm".
>>>> +- address-cells: should be 1
>>>> +- size-cells: should be 0
>>>> +
>>>> +Sub-nodes:
>>>> +  Each port's PHY should be represented as a sub-node.
>>>> +
>>>> +Sub-nodes required properties:
>>>> +- reg: the PHY number
>>>> +- #phy-cells must be 1
>>>> +  The node that uses the phy must provide 1 integer argument specifying
>>>> +  port number.
>>>> +
>>>> +Optional Properties:
>>>> +- vbus-p#-supply : The regulator for vbus out control for the host
>>>
>>> Is this a literal # or something else?
>>
>> Yes, this is a literal. It's assumed # will replace numeric 0-2 for
>> each of the ports.
>
> I'm still confused. Which is valid? "vbus-p#-supply" or "vbus-p0-supply"
>
I agree, it's creating confusion. Instead of enumerating
"vbus-p0-supply", "vbus-p1-supply", "vbus-p2-supply" kept "vbus-p#-supply".

Yes, as suggested by you "vbus-supply" should be sufficient as it's in each
of phy sub node.

> If the latter, you need to enumerate all valid options. But these are
> in sub nodes for each port, so just "vbus-supply" should be
> sufficient.

Keeping "vbus-supply" should not create any confusion. Will send out the
change in next version of the patch.

> One more question, does Vbus actually supply power to the phy or you
> are just associating the Vbus supply to a connector with a port? The
> latter needs a connector node instead and Vbus should be part of that.
> There's been some attempts at USB connectors, but we don't really have
> one yet (the extcon binding is not it).

Vbus is not supplied to phy, it's been given to the devices connected on
the port and in our platform vbus is controlled through an external regulator
which is controlled through gpio.
So "vbus-supply" shown above actually points to the phandle of vbus regulator
node.

>> In the example it's not shown as the regulators specified in vbus-p#-supply
>> are board specific.
>
> Please show in the example. Examples should be complete.

Ok. Sure.

> Rob
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding
@ 2017-11-14  5:29           ` Raveendra Padasalagi
  0 siblings, 0 replies; 29+ messages in thread
From: Raveendra Padasalagi @ 2017-11-14  5:29 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

On Mon, Nov 13, 2017 at 11:23 PM, Rob Herring <robh@kernel.org> wrote:
> On Sun, Nov 12, 2017 at 10:23 PM, Raveendra Padasalagi
> <raveendra.padasalagi@broadcom.com> wrote:
>> Hi,
>>
>> On Sat, Nov 11, 2017 at 3:14 AM, Rob Herring <robh@kernel.org> wrote:
>>> On Wed, Nov 08, 2017 at 01:16:41PM +0530, Raveendra Padasalagi wrote:
>>>> Add devicetree binding document for broadcom's
>>>> Cygnus SoC specific usb phy controller driver.
>>>>
>>>> Signed-off-by: Raveendra Padasalagi <raveendra.padasalagi@broadcom.com>
>>>> ---
>>>>  .../bindings/phy/brcm,cygnus-usb-phy.txt           | 106 +++++++++++++++++++++
>>>>  1 file changed, 106 insertions(+)
>>>>  create mode 100644 Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>> new file mode 100644
>>>> index 0000000..bbc4b94
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/phy/brcm,cygnus-usb-phy.txt
>>>> @@ -0,0 +1,106 @@
>>>> +BROADCOM CYGNUS USB PHY
>>>> +
>>>> +Required Properties:
>>>> +- compatible:  brcm,cygnus-usb-phy
>>>> +- reg : the register start address and length for
>>>> +        crmu_usbphy_aon_ctrl,
>>>> +        cdru usb phy control,
>>>> +        usb host idm registers,
>>>> +        usb device idm registers.
>>>> +- reg-names: a list of the names corresponding to the previous register ranges
>>>> +  Should contain
>>>> +        "crmu-usbphy-aon-ctrl",
>>>> +        "cdru-usbphy",
>>>> +        "usb2h-idm",
>>>> +        "usb2d-idm".
>>>> +- address-cells: should be 1
>>>> +- size-cells: should be 0
>>>> +
>>>> +Sub-nodes:
>>>> +  Each port's PHY should be represented as a sub-node.
>>>> +
>>>> +Sub-nodes required properties:
>>>> +- reg: the PHY number
>>>> +- #phy-cells must be 1
>>>> +  The node that uses the phy must provide 1 integer argument specifying
>>>> +  port number.
>>>> +
>>>> +Optional Properties:
>>>> +- vbus-p#-supply : The regulator for vbus out control for the host
>>>
>>> Is this a literal # or something else?
>>
>> Yes, this is a literal. It's assumed # will replace numeric 0-2 for
>> each of the ports.
>
> I'm still confused. Which is valid? "vbus-p#-supply" or "vbus-p0-supply"
>
I agree, it's creating confusion. Instead of enumerating
"vbus-p0-supply", "vbus-p1-supply", "vbus-p2-supply" kept "vbus-p#-supply".

Yes, as suggested by you "vbus-supply" should be sufficient as it's in each
of phy sub node.

> If the latter, you need to enumerate all valid options. But these are
> in sub nodes for each port, so just "vbus-supply" should be
> sufficient.

Keeping "vbus-supply" should not create any confusion. Will send out the
change in next version of the patch.

> One more question, does Vbus actually supply power to the phy or you
> are just associating the Vbus supply to a connector with a port? The
> latter needs a connector node instead and Vbus should be part of that.
> There's been some attempts at USB connectors, but we don't really have
> one yet (the extcon binding is not it).

Vbus is not supplied to phy, it's been given to the devices connected on
the port and in our platform vbus is controlled through an external regulator
which is controlled through gpio.
So "vbus-supply" shown above actually points to the phandle of vbus regulator
node.

>> In the example it's not shown as the regulators specified in vbus-p#-supply
>> are board specific.
>
> Please show in the example. Examples should be complete.

Ok. Sure.

> Rob

^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2017-11-14  5:29 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-08  7:46 [PATCH v2 0/3] Add driver for Broadcom Cygnus USB phy controller Raveendra Padasalagi
2017-11-08  7:46 ` Raveendra Padasalagi
2017-11-08  7:46 ` [PATCH v2 1/3] dt-bindings: phy: Add Cygnus usb phy binding Raveendra Padasalagi
2017-11-08  7:46   ` Raveendra Padasalagi
2017-11-10 21:44   ` Rob Herring
2017-11-10 21:44     ` Rob Herring
2017-11-13  4:23     ` Raveendra Padasalagi
2017-11-13  4:23       ` Raveendra Padasalagi
2017-11-13  4:23       ` Raveendra Padasalagi
2017-11-13 17:53       ` Rob Herring
2017-11-13 17:53         ` Rob Herring
2017-11-13 17:53         ` Rob Herring
2017-11-14  5:29         ` Raveendra Padasalagi
2017-11-14  5:29           ` Raveendra Padasalagi
2017-11-14  5:29           ` Raveendra Padasalagi
2017-11-08  7:46 ` [PATCH v2 2/3] drivers: phy: broadcom: Add driver for Cygnus USB phy controller Raveendra Padasalagi
2017-11-08  7:46   ` Raveendra Padasalagi
2017-11-08  7:46   ` Raveendra Padasalagi
2017-11-08  7:52   ` Raveendra Padasalagi
2017-11-08  7:52     ` Raveendra Padasalagi
2017-11-08  7:52     ` Raveendra Padasalagi
2017-11-08 10:36     ` Chanwoo Choi
2017-11-08 10:36       ` Chanwoo Choi
2017-11-08 10:36       ` Chanwoo Choi
2017-11-08 16:17       ` Raveendra Padasalagi
2017-11-08 16:17         ` Raveendra Padasalagi
2017-11-08 16:17         ` Raveendra Padasalagi
2017-11-08  7:46 ` [PATCH v2 3/3] ARM: dts: Add dt node for Broadcom Cygnus USB phy Raveendra Padasalagi
2017-11-08  7:46   ` Raveendra Padasalagi

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.