linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v2 00/15]  Introduced new Cadence USBSS DRD Driver
@ 2018-11-18 10:08 Pawel Laszczak
  2018-11-18 10:08 ` [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper Pawel Laszczak
                   ` (14 more replies)
  0 siblings, 15 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:08 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

This patch set introduce new Cadence USBSS DRD driver
to linux kernel.

The Cadence USBSS DRD Driver s a highly
configurable IP Core which can be
instantiated as Dual-Role Device (DRD),
Peripheral Only and Host Only (XHCI)
configurations.

The current driver has been validated with
FPGA burned. We have support for PCIe
bus, which is used on FPGA prototyping.

The host site of USBSS controller is compliance
with XHCI specification, so it works with
standard XHCI linux driver.

Changes since v1:
 - Reorganize patch 3, 5, 6 as suggested by Roger Quadros.
 - Remove inline in debug.c file according with Joe Perches suggestion.
 - Fix condition with no effect in drd.c file.
 - Remove compiler warning generated by sh4-linux-gnu-gcc.
 - Fix bug with xhci_suspend/resume undefined, that appeared for PM disabled.
 - Add CONFIG_OF support in core.c file and dt-binding documentation.
 - Add template function related to CONFIG_PM configuration option.
 - Fix bug: CONFIG_USB_CDNS3_DEVICE instead of CONFIG_USB_CDNS3_
 - Correct depend on condition in Kconfig as suggested by Roger Quadros.
 - Remove Config options from cdns3_pci_wrap.c as suggested by Roger Quadros.
 - Replace dev_info with dev_dbg in cdns3_pci_wrap.c file as suggested by Roger.
 - Change cdns3_role to cnds3_get_current_role_driver as suggested by Roger.
 - Addressed other review comments from Roger.
 - Fix issues with ENABLE_U1/U2 Set/Clear_Fature request.
 - Fix issues with Cycle State bit.
 - Some other minor code changes related to readability.

TODO:
 - Test isochronous transfer with some class or tester.
 - Test changing role according to ID pin.
 - Resolve issue related with shared on-chip buffer for OUT direction.
 - Add tracepoint. 
 - Implement suspend/resume functionality.

---

Pawel Laszczak (15):
  usb:cdns3: add pci to platform driver wrapper.
  usb:cdns3: Device side header file.
  dt-bindings: add binding for USBSS-DRD controller.
  usb:cdns3: Driver initialization code.
  usb:cdns3: Added DRD support
  usb:cdns3: Adds Host support
  usb:cdns3: Adds Device mode support - initialization.
  usb:cdns3: Implements device operations part of the API
  usb:cdns3: EpX operations part of the API
  usb:cdns3: Ep0 operations part of the API
  usb:cdns3: Implements ISR functionality.
  usb:cdns3: Adds enumeration related function.
  usb:cdns3: Adds transfer related function.
  usb:cdns3: Adds debugging function.
  usb:cdns3: Feature for changing role

 .../devicetree/bindings/usb/cdns3-usb.txt     |   17 +
 drivers/usb/Kconfig                           |    2 +
 drivers/usb/Makefile                          |    2 +
 drivers/usb/cdns3/Kconfig                     |   44 +
 drivers/usb/cdns3/Makefile                    |    7 +
 drivers/usb/cdns3/cdns3-pci-wrap.c            |  157 ++
 drivers/usb/cdns3/core.c                      |  434 +++++
 drivers/usb/cdns3/core.h                      |  100 +
 drivers/usb/cdns3/debug.c                     |  128 ++
 drivers/usb/cdns3/debugfs.c                   |   93 +
 drivers/usb/cdns3/drd.c                       |  229 +++
 drivers/usb/cdns3/drd.h                       |  125 ++
 drivers/usb/cdns3/ep0.c                       |  859 +++++++++
 drivers/usb/cdns3/gadget-export.h             |   27 +
 drivers/usb/cdns3/gadget.c                    | 1665 +++++++++++++++++
 drivers/usb/cdns3/gadget.h                    | 1104 +++++++++++
 drivers/usb/cdns3/host-export.h               |   30 +
 drivers/usb/cdns3/host.c                      |  256 +++
 18 files changed, 5279 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/cdns3-usb.txt
 create mode 100644 drivers/usb/cdns3/Kconfig
 create mode 100644 drivers/usb/cdns3/Makefile
 create mode 100644 drivers/usb/cdns3/cdns3-pci-wrap.c
 create mode 100644 drivers/usb/cdns3/core.c
 create mode 100644 drivers/usb/cdns3/core.h
 create mode 100644 drivers/usb/cdns3/debug.c
 create mode 100644 drivers/usb/cdns3/debugfs.c
 create mode 100644 drivers/usb/cdns3/drd.c
 create mode 100644 drivers/usb/cdns3/drd.h
 create mode 100644 drivers/usb/cdns3/ep0.c
 create mode 100644 drivers/usb/cdns3/gadget-export.h
 create mode 100644 drivers/usb/cdns3/gadget.c
 create mode 100644 drivers/usb/cdns3/gadget.h
 create mode 100644 drivers/usb/cdns3/host-export.h
 create mode 100644 drivers/usb/cdns3/host.c

-- 
2.17.1


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

* [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
@ 2018-11-18 10:08 ` Pawel Laszczak
  2018-11-23 10:44   ` Roger Quadros
  2018-11-18 10:08 ` [RFC PATCH v2 02/15] usb:cdns3: Device side header file Pawel Laszczak
                   ` (13 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:08 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds PCI specific glue driver that creates and registers in-system
cdns-usb3 platform device. Thanks to that we will be able to use
the cdns-usb3 platform driver for USBSS-DEV controller
build on PCI board

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/Kconfig                |   2 +
 drivers/usb/Makefile               |   2 +
 drivers/usb/cdns3/Kconfig          |  24 +++++
 drivers/usb/cdns3/Makefile         |   3 +
 drivers/usb/cdns3/cdns3-pci-wrap.c | 157 +++++++++++++++++++++++++++++
 5 files changed, 188 insertions(+)
 create mode 100644 drivers/usb/cdns3/Kconfig
 create mode 100644 drivers/usb/cdns3/Makefile
 create mode 100644 drivers/usb/cdns3/cdns3-pci-wrap.c

diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 987fc5ba6321..5f9334019d04 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -112,6 +112,8 @@ source "drivers/usb/usbip/Kconfig"
 
 endif
 
+source "drivers/usb/cdns3/Kconfig"
+
 source "drivers/usb/mtu3/Kconfig"
 
 source "drivers/usb/musb/Kconfig"
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 7d1b8c82b208..82093a60ea2c 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -8,6 +8,8 @@
 obj-$(CONFIG_USB)		+= core/
 obj-$(CONFIG_USB_SUPPORT)	+= phy/
 
+obj-$(CONFIG_USB_CDNS3)		+= cdns3/
+
 obj-$(CONFIG_USB_DWC3)		+= dwc3/
 obj-$(CONFIG_USB_DWC2)		+= dwc2/
 obj-$(CONFIG_USB_ISP1760)	+= isp1760/
diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
new file mode 100644
index 000000000000..eb22a8692991
--- /dev/null
+++ b/drivers/usb/cdns3/Kconfig
@@ -0,0 +1,24 @@
+config USB_CDNS3
+	tristate "Cadence USB3 Dual-Role Controller"
+	depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA
+	help
+	  Say Y here if your system has a cadence USB3 dual-role controller.
+	  It supports: dual-role switch, Host-only, and Peripheral-only.
+
+	  If you choose to build this driver is a dynamically linked
+	  module, the module will be called cdns3.ko.
+
+if USB_CDNS3
+
+config USB_CDNS3_PCI_WRAP
+	tristate "PCIe-based Platforms"
+	depends on USB_PCI && ACPI
+	default USB_CDNS3
+	help
+	  If you're using the USBSS Core IP with a PCIe, please say
+	  'Y' or 'M' here.
+
+	  If you choose to build this driver as module it will
+	  be dynamically linked and module will be called cdns3-pci.ko
+
+endif
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
new file mode 100644
index 000000000000..dcdd62003c6a
--- /dev/null
+++ b/drivers/usb/cdns3/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
+
+cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c
new file mode 100644
index 000000000000..d0a15cc0b738
--- /dev/null
+++ b/drivers/usb/cdns3/cdns3-pci-wrap.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS PCI Glue driver
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak <pawell@cadence.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+
+struct cdns3_wrap {
+	struct platform_device *plat_dev;
+	struct pci_dev *hg_dev;
+	struct resource dev_res[4];
+};
+
+struct cdns3_wrap wrap;
+
+#define RES_IRQ_ID		0
+#define RES_HOST_ID		1
+#define RES_DEV_ID		2
+#define RES_DRD_ID		3
+
+#define PCI_BAR_HOST		0
+#define PCI_BAR_DEV		2
+#define PCI_BAR_OTG		4
+
+#define PCI_DEV_FN_HOST_DEVICE	0
+#define PCI_DEV_FN_OTG		1
+
+#define PCI_DRIVER_NAME		"cdns3-pci-usbss"
+#define PLAT_DRIVER_NAME	"cdns-usb3"
+
+#define CDNS_VENDOR_ID 0x17cd
+#define CDNS_DEVICE_ID 0x0100
+
+/**
+ * cdns3_pci_probe - Probe function for Cadence USB wrapper driver
+ * @pdev: platform device object
+ * @id: pci device id
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_pci_probe(struct pci_dev *pdev,
+			   const struct pci_device_id *id)
+{
+	struct platform_device_info plat_info;
+	struct cdns3_wrap *wrap;
+	struct resource *res;
+	int err;
+
+	/*
+	 * for GADGET/HOST PCI (devfn) function number is 0,
+	 * for OTG PCI (devfn) function number is 1
+	 */
+	if (!id || pdev->devfn != PCI_DEV_FN_HOST_DEVICE)
+		return -EINVAL;
+
+	err = pcim_enable_device(pdev);
+	if (err) {
+		dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", err);
+		return err;
+	}
+
+	pci_set_master(pdev);
+	wrap = devm_kzalloc(&pdev->dev, sizeof(*wrap), GFP_KERNEL);
+	if (!wrap) {
+		dev_err(&pdev->dev, "Failed to load PCI module\n");
+		return -ENOMEM;
+	}
+
+	/* function 0: host(BAR_0) + device(BAR_1) + otg(BAR_2)). */
+	memset(wrap->dev_res, 0x00,
+	       sizeof(struct resource) * ARRAY_SIZE(wrap->dev_res));
+	dev_dbg(&pdev->dev, "Initialize Device resources\n");
+	res = wrap->dev_res;
+
+	res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV);
+	res[RES_DEV_ID].end =   pci_resource_end(pdev, PCI_BAR_DEV);
+	res[RES_DEV_ID].name = "cdns3-dev-regs";
+	res[RES_DEV_ID].flags = IORESOURCE_MEM;
+	dev_dbg(&pdev->dev, "USBSS-DEV physical base addr: %pa\n",
+		&res[RES_DEV_ID].start);
+
+	res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST);
+	res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST);
+	res[RES_HOST_ID].name = "cdns3-xhci-regs";
+	res[RES_HOST_ID].flags = IORESOURCE_MEM;
+	dev_dbg(&pdev->dev, "USBSS-XHCI physical base addr: %pa\n",
+		&res[RES_HOST_ID].start);
+
+	res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG);
+	res[RES_DRD_ID].end =   pci_resource_end(pdev, PCI_BAR_OTG);
+	res[RES_DRD_ID].name = "cdns3-otg";
+	res[RES_DRD_ID].flags = IORESOURCE_MEM;
+	dev_dbg(&pdev->dev, "USBSS-DRD physical base addr: %pa\n",
+		&res[RES_DRD_ID].start);
+
+	/* Interrupt common for both device and XHCI */
+	wrap->dev_res[RES_IRQ_ID].start = pdev->irq;
+	wrap->dev_res[RES_IRQ_ID].name = "cdns3-irq";
+	wrap->dev_res[RES_IRQ_ID].flags = IORESOURCE_IRQ;
+
+	/* set up platform device info */
+	memset(&plat_info, 0, sizeof(plat_info));
+	plat_info.parent = &pdev->dev;
+	plat_info.fwnode = pdev->dev.fwnode;
+	plat_info.name = PLAT_DRIVER_NAME;
+	plat_info.id = pdev->devfn;
+	plat_info.res = wrap->dev_res;
+	plat_info.num_res = ARRAY_SIZE(wrap->dev_res);
+	plat_info.dma_mask = pdev->dma_mask;
+
+	/* register platform device */
+	wrap->plat_dev = platform_device_register_full(&plat_info);
+	if (IS_ERR(wrap->plat_dev)) {
+		err = PTR_ERR(wrap->plat_dev);
+		return err;
+	}
+
+	pci_set_drvdata(pdev, wrap);
+
+	return err;
+}
+
+void cdns3_pci_remove(struct pci_dev *pdev)
+{
+	struct cdns3_wrap *wrap = (struct cdns3_wrap *)pci_get_drvdata(pdev);
+
+	platform_device_unregister(wrap->plat_dev);
+}
+
+static const struct pci_device_id cdns3_pci_ids[] = {
+	{ PCI_DEVICE(CDNS_VENDOR_ID, CDNS_DEVICE_ID), },
+	{ 0, }
+};
+
+static struct pci_driver cdns3_pci_driver = {
+	.name = PCI_DRIVER_NAME,
+	.id_table = cdns3_pci_ids,
+	.probe = cdns3_pci_probe,
+	.remove = cdns3_pci_remove,
+};
+
+module_pci_driver(cdns3_pci_driver);
+MODULE_DEVICE_TABLE(pci, cdns3_pci_ids);
+
+MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Cadence USBSS PCI wrapperr");
+
-- 
2.17.1


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

* [RFC PATCH v2 02/15] usb:cdns3: Device side header file.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
  2018-11-18 10:08 ` [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper Pawel Laszczak
@ 2018-11-18 10:08 ` Pawel Laszczak
  2018-11-30  6:48   ` PETER CHEN
  2018-11-18 10:08 ` [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller Pawel Laszczak
                   ` (12 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:08 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch defines macros used by device side of controller,
structures holding registers, and some other
object used by device controller.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/gadget.h | 1071 ++++++++++++++++++++++++++++++++++++
 1 file changed, 1071 insertions(+)
 create mode 100644 drivers/usb/cdns3/gadget.h

diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
new file mode 100644
index 000000000000..75ca6214e79a
--- /dev/null
+++ b/drivers/usb/cdns3/gadget.h
@@ -0,0 +1,1071 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * USBSS device controller driver
+ *
+ * Copyright (C) 2018 Cadence.
+ * Copyright (C) 2018 NXP
+ *
+ * Author: Pawel Laszczak <pawell@cadence.com>
+ *         Pawel Jez <pjez@cadence.com>
+ *         Peter Chen <peter.chen@nxp.com>
+ */
+#ifndef __LINUX_CDNS3_GADGET
+#define __LINUX_CDNS3_GADGET
+#include <linux/usb/gadget.h>
+
+/*
+ * USBSS-DEV register interface.
+ * This corresponds to the USBSS Device Controller Interface
+ */
+
+/**
+ * struct xhci_cap_regs - xHCI Host Controller Capability Registers.
+ * @usb_conf:      Global Configuration Register.
+ * @usb_sts:       Global Status Register.
+ * @usb_cmd:       Global Command Register.
+ * @usb_iptn:      ITP/SOF number Register.
+ * @usb_lpm:       Global Command Register.
+ * @usb_ien:       USB Interrupt Enable Register.
+ * @usb_ists:      USB Interrupt Status Register.
+ * @ep_sel:        Endpoint Select Register.
+ * @ep_traddr:     Endpoint Transfer Ring Address Register.
+ * @ep_cfg:        Endpoint Configuration Register.
+ * @ep_cmd:        Endpoint Command Register.
+ * @ep_sts:        Endpoint Status Register.
+ * @ep_sts_sid:    Endpoint Status Register.
+ * @ep_sts_en:     Endpoint Status Register Enable.
+ * @drbl:          Doorbell Register.
+ * @ep_ien:        EP Interrupt Enable Register.
+ * @ep_ists:       EP Interrupt Status Register.
+ * @usb_pwr:       Global Power Configuration Register.
+ * @usb_conf2:     Global Configuration Register 2.
+ * @usb_cap1:      Capability Register 1.
+ * @usb_cap2:      Capability Register 2.
+ * @usb_cap3:      Capability Register 3.
+ * @usb_cap4:      Capability Register 4.
+ * @usb_cap5:      Capability Register 5.
+ * @usb_cap6:      Capability Register 6.
+ * @usb_cpkt1:     Custom Packet Register 1.
+ * @usb_cpkt2:     Custom Packet Register 2.
+ * @usb_cpkt3:     Custom Packet Register 3.
+ * @reserved1:     Reserved.
+ * @cfg_regs:      Configuration registers.
+ * @reserved2:     Reserved.
+ * @dma_axi_ctrl:  AXI Control register.
+ * @dma_axi_id:    AXI ID register.
+ * @dma_axi_cap:   AXI Capability register.
+ * @dma_axi_ctrl0: AXI Control 0 register.
+ * @dma_axi_ctrl1: AXI Control 1 register.
+ */
+struct cdns3_usb_regs {
+	__le32 usb_conf;
+	__le32 usb_sts;
+	__le32 usb_cmd;
+	__le32 usb_iptn;
+	__le32 usb_lpm;
+	__le32 usb_ien;
+	__le32 usb_ists;
+	__le32 ep_sel;
+	__le32 ep_traddr;
+	__le32 ep_cfg;
+	__le32 ep_cmd;
+	__le32 ep_sts;
+	__le32 ep_sts_sid;
+	__le32 ep_sts_en;
+	__le32 drbl;
+	__le32 ep_ien;
+	__le32 ep_ists;
+	__le32 usb_pwr;
+	__le32 usb_conf2;
+	__le32 usb_cap1;
+	__le32 usb_cap2;
+	__le32 usb_cap3;
+	__le32 usb_cap4;
+	__le32 usb_cap5;
+	__le32 usb_cap6;
+	__le32 usb_cpkt1;
+	__le32 usb_cpkt2;
+	__le32 usb_cpkt3;
+	__le32 reserved1[36];
+	__le32 cfg_reg1;
+	__le32 dbg_link1;
+	__le32 dbg_link2;
+	__le32 cfg_regs[74];
+	__le32 reserved2[34];
+	__le32 dma_axi_ctrl;
+	__le32 dma_axi_id;
+	__le32 dma_axi_cap;
+	__le32 dma_axi_ctrl0;
+	__le32 dma_axi_ctrl1;
+};
+
+/* USB_CONF - bitmasks */
+/* Reset USB device configuration. */
+#define USB_CONF_CFGRST		BIT(0)
+/* Set Configuration. */
+#define USB_CONF_CFGSET		BIT(1)
+/* Disconnect USB device in SuperSpeed. */
+#define USB_CONF_USB3DIS	BIT(3)
+/* Disconnect USB device in HS/FS */
+#define USB_CONF_USB2DIS	BIT(4)
+/* Little Endian access - default */
+#define USB_CONF_LENDIAN	BIT(5)
+/*
+ * Big Endian access. Driver assume that byte order for
+ * SFRs access always is as Little Endian so this bit
+ * is not used.
+ */
+#define USB_CONF_BENDIAN	BIT(6)
+/* Device software reset. */
+#define USB_CONF_SWRST		BIT(7)
+/* Singular DMA transfer mode. */
+#define USB_CONF_DSING		BIT(8)
+/* Multiple DMA transfers mode. */
+#define USB_CONF_DMULT		BIT(9)
+/* DMA clock turn-off enable. */
+#define USB_CONF_DMAOFFEN	BIT(10)
+/* DMA clock turn-off disable. */
+#define USB_CONF_DMAOFFDS	BIT(11)
+/* Clear Force Full Speed. */
+#define USB_CONF_CFORCE_FS	BIT(12)
+/* Set Force Full Speed. */
+#define USB_CONF_SFORCE_FS	BIT(13)
+/* Device enable. */
+#define USB_CONF_DEVEN		BIT(14)
+/* Device disable. */
+#define USB_CONF_DEVDS		BIT(15)
+/* L1 LPM state entry enable (used in HS/FS mode). */
+#define USB_CONF_L1EN		BIT(16)
+/* L1 LPM state entry disable (used in HS/FS mode). */
+#define USB_CONF_L1DS		BIT(17)
+/* USB 2.0 clock gate disable. */
+#define USB_CONF_CLK2OFFEN	BIT(18)
+/* USB 2.0 clock gate enable. */
+#define USB_CONF_CLK2OFFDS	BIT(19)
+/* L0 LPM state entry request (used in HS/FS mode). */
+#define USB_CONF_LGO_L0		BIT(20)
+/* USB 3.0 clock gate disable. */
+#define USB_CONF_CLK3OFFEN	BIT(21)
+/* USB 3.0 clock gate enable. */
+#define USB_CONF_CLK3OFFDS	BIT(22)
+/* Bit 23 is reserved*/
+/* U1 state entry enable (used in SS mode). */
+#define USB_CONF_U1EN		BIT(24)
+/* U1 state entry disable (used in SS mode). */
+#define USB_CONF_U1DS		BIT(25)
+/* U2 state entry enable (used in SS mode). */
+#define USB_CONF_U2EN		BIT(26)
+/* U2 state entry disable (used in SS mode). */
+#define USB_CONF_U2DS		BIT(27)
+/* U0 state entry request (used in SS mode). */
+#define USB_CONF_LGO_U0		BIT(28)
+/* U1 state entry request (used in SS mode). */
+#define USB_CONF_LGO_U1		BIT(29)
+/* U2 state entry request (used in SS mode). */
+#define USB_CONF_LGO_U2		BIT(30)
+/* SS.Inactive state entry request (used in SS mode) */
+#define USB_CONF_LGO_SSINACT	BIT(31)
+
+/* USB_STS - bitmasks */
+/*
+ * Configuration status.
+ * 1 - device is in the configured state.
+ * 0 - device is not configured.
+ */
+#define USB_STS_CFGSTS_MASK	BIT(0)
+#define USB_STS_CFGSTS(p)	((p) & USB_STS_CFGSTS_MASK)
+/*
+ * On-chip memory overflow.
+ * 0 - On-chip memory status OK.
+ * 1 - On-chip memory overflow.
+ */
+#define USB_STS_OV_MASK		BIT(1)
+#define USB_STS_OV(p)		((p) & USB_STS_OV_MASK)
+/*
+ * SuperSpeed connection status.
+ * 0 - USB in SuperSpeed mode disconnected.
+ * 1 - USB in SuperSpeed mode connected.
+ */
+#define USB_STS_USB3CONS_MASK	BIT(2)
+#define USB_STS_USB3CONS(p)	((p) & USB_STS_USB3CONS_MASK)
+/*
+ * DMA transfer configuration status.
+ * 0 - single request.
+ * 1 - multiple TRB chain
+ */
+#define USB_STS_DTRANS_MASK	BIT(3)
+#define USB_STS_DTRANS(p)	((p) & USB_STS_DTRANS_MASK)
+/*
+ * Device speed.
+ * 0 - Undefined (value after reset).
+ * 1 - Low speed
+ * 2 - Full speed
+ * 3 - High speed
+ * 4 - Super speed
+ */
+#define USB_STS_USBSPEED_MASK	GENMASK(6, 4)
+#define USB_STS_USBSPEED(p)	(((p) & USB_STS_USBSPEED_MASK) >> 4)
+#define USB_STS_LS		(0x1 << 4)
+#define USB_STS_FS		(0x2 << 4)
+#define USB_STS_HS		(0x3 << 4)
+#define USB_STS_SS		(0x4 << 4)
+#define DEV_UNDEFSPEED(p)	(((p) & USB_STS_USBSPEED_MASK) == (0x0 << 4))
+#define DEV_LOWSPEED(p)		(((p) & USB_STS_USBSPEED_MASK) == USB_STS_LS)
+#define DEV_FULLSPEED(p)	(((p) & USB_STS_USBSPEED_MASK) == USB_STS_FS)
+#define DEV_HIGHSPEED(p)	(((p) & USB_STS_USBSPEED_MASK) == USB_STS_HS)
+#define DEV_SUPERSPEED(p)	(((p) & USB_STS_USBSPEED_MASK) == USB_STS_SS)
+/*
+ * Endianness for SFR access.
+ * 0 - Little Endian order (default after hardware reset).
+ * 1 - Big Endian order
+ */
+#define USB_STS_ENDIAN_MASK	BIT(7)
+#define USB_STS_ENDIAN(p)	((p) & USB_STS_ENDIAN_MASK)
+/*
+ * HS/FS clock turn-off status.
+ * 0 - hsfs clock is always on.
+ * 1 - hsfs clock turn-off in L2 (HS/FS mode) is enabled
+ *          (default after hardware reset).
+ */
+#define USB_STS_CLK2OFF_MASK	BIT(8)
+#define USB_STS_CLK2OFF(p)	((p) & USB_STS_CLK2OFF_MASK)
+/*
+ * PCLK clock turn-off status.
+ * 0 - pclk clock is always on.
+ * 1 - pclk clock turn-off in U3 (SS mode) is enabled
+ *          (default after hardware reset).
+ */
+#define USB_STS_CLK3OFF_MASK	BIT(9)
+#define USB_STS_CLK3OFF(p)	((p) & USB_STS_CLK3OFF_MASK)
+/*
+ * Controller in reset state.
+ * 0 - Internal reset is active.
+ * 1 - Internal reset is not active and controller is fully operational.
+ */
+#define USB_STS_IN_RST_MASK	BIT(10)
+#define USB_STS_IN_RST(p)	((p) & USB_STS_IN_RST_MASK)
+/*
+ * Device enable Status.
+ * 0 - USB device is disabled (VBUS input is disconnected from internal logic).
+ * 1 - USB device is enabled (VBUS input is connected to the internal logic).
+ */
+#define USB_STS_DEVS_MASK	BIT(14)
+#define USB_STS_DEVS(p)		((p) & USB_STS_DEVS_MASK)
+/*
+ * DAddress statuss.
+ * 0 - USB device is default state.
+ * 1 - USB device is at least in address state.
+ */
+#define USB_STS_ADDRESSED_MASK	BIT(15)
+#define USB_STS_ADDRESSED(p)	((p) & USB_STS_ADDRESSED_MASK)
+/*
+ * L1 LPM state enable status (used in HS/FS mode).
+ * 0 - Entering to L1 LPM state disabled.
+ * 1 - Entering to L1 LPM state enabled.
+ */
+#define USB_STS_L1ENS_MASK	BIT(16)
+#define USB_STS_L1ENS(p)	((p) & USB_STS_L1ENS_MASK)
+/*
+ * Internal VBUS connection status (used both in HS/FS  and SS mode).
+ * 0 - internal VBUS is not detected.
+ * 1 - internal VBUS is detected.
+ */
+#define USB_STS_VBUSS_MASK	BIT(17)
+#define USB_STS_VBUSS(p)	((p) & USB_STS_VBUSS_MASK)
+/*
+ * HS/FS LPM  state (used in FS/HS mode).
+ * 0 - L0 State
+ * 1 - L1 State
+ * 2 - L2 State
+ * 3 - L3 State
+ */
+#define USB_STS_LPMST_MASK	GENMASK(19, 18)
+#define DEV_L0_STATE(p)		(((p) & USB_STS_LPMST_MASK) == (0x0 << 18))
+#define DEV_L1_STATE(p)		(((p) & USB_STS_LPMST_MASK) == (0x1 << 18))
+#define DEV_L2_STATE(p)		(((p) & USB_STS_LPMST_MASK) == (0x2 << 18))
+#define DEV_L3_STATE(p)		(((p) & USB_STS_LPMST_MASK) == (0x3 << 18))
+/*
+ * Disable HS status (used in FS/HS mode).
+ * 0 - the disconnect bit for HS/FS mode is set .
+ * 1 - the disconnect bit for HS/FS mode is not set.
+ */
+#define USB_STS_USB2CONS_MASK	BIT(20)
+#define USB_STS_USB2CONS(p)	((p) & USB_STS_USB2CONS_MASK)
+/*
+ * HS/FS mode connection status (used in FS/HS mode).
+ * 0 - High Speed operations in USB2.0 (FS/HS) mode not disabled.
+ * 1 - High Speed operations in USB2.0 (FS/HS).
+ */
+#define USB_STS_DISABLE_HS_MASK	BIT(21)
+#define USB_STS_DISABLE_HS(p)	((p) & USB_STS_DISABLE_HS_MASK)
+/*
+ * U1 state enable status (used in SS mode).
+ * 0 - Entering to  U1 state disabled.
+ * 1 - Entering to  U1 state enabled.
+ */
+#define USB_STS_U1ENS_MASK	BIT(24)
+#define USB_STS_U1ENS(p)	((p) & USB_STS_U1ENS_MASK)
+/*
+ * U2 state enable status (used in SS mode).
+ * 0 - Entering to  U2 state disabled.
+ * 1 - Entering to  U2 state enabled.
+ */
+#define USB_STS_U2ENS_MASK	BIT(25)
+#define USB_STS_U2ENS(p)	((p) & USB_STS_U2ENS_MASK)
+/*
+ * SuperSpeed Link LTSSM state. This field reflects USBSS-DEV current
+ * SuperSpeed link state
+ */
+#define USB_STS_LST_MASK	GENMASK(29, 26)
+#define DEV_LST_U0		(((p) & USB_STS_LST_MASK) == (0x0 << 26))
+#define DEV_LST_U1		(((p) & USB_STS_LST_MASK) == (0x1 << 26))
+#define DEV_LST_U2		(((p) & USB_STS_LST_MASK) == (0x2 << 26))
+#define DEV_LST_U3		(((p) & USB_STS_LST_MASK) == (0x3 << 26))
+#define DEV_LST_DISABLED	(((p) & USB_STS_LST_MASK) == (0x4 << 26))
+#define DEV_LST_RXDETECT	(((p) & USB_STS_LST_MASK) == (0x5 << 26))
+#define DEV_LST_INACTIVE	(((p) & USB_STS_LST_MASK) == (0x6 << 26))
+#define DEV_LST_POLLING		(((p) & USB_STS_LST_MASK) == (0x7 << 26))
+#define DEV_LST_RECOVERY	(((p) & USB_STS_LST_MASK) == (0x8 << 26))
+#define DEV_LST_HOT_RESET	(((p) & USB_STS_LST_MASK) == (0x9 << 26))
+#define DEV_LST_COMP_MODE	(((p) & USB_STS_LST_MASK) == (0xa << 26))
+#define DEV_LST_LB_STATE	(((p) & USB_STS_LST_MASK) == (0xb << 26))
+/*
+ * DMA clock turn-off status.
+ * 0 - DMA clock is always on (default after hardware reset).
+ * 1 - DMA clock turn-off in U1, U2 and U3 (SS mode) is enabled.
+ */
+#define USB_STS_DMAOFF_MASK	BIT(30)
+#define USB_STS_DMAOFF(p)	((p) & USB_STS_DMAOFF_MASK)
+/*
+ * SFR Endian statuss.
+ * 0 - Little Endian order (default after hardware reset).
+ * 1 - Big Endian order.
+ */
+#define USB_STS_ENDIAN2_MASK	BIT(31)
+#define USB_STS_ENDIAN2(p)	((p) & USB_STS_ENDIAN2_MASK)
+
+/* USB_CMD -  bitmasks */
+/* Set Function Address */
+#define USB_CMD_SET_ADDR	BIT(0)
+/*
+ * Function Address This field is saved to the device only when the field
+ * SET_ADDR is set '1 ' during write to USB_CMD register.
+ * Software is responsible for entering the address of the device during
+ * SET_ADDRESS request service. This field should be set immediately after
+ * the SETUP packet is decoded, and prior to confirmation of the status phase
+ */
+#define USB_CMD_FADDR_MASK	GENMASK(7, 1)
+#define USB_CMD_FADDR(p)	(((p) << 1) & USB_CMD_FADDR_MASK)
+/* Send Function Wake Device Notification TP (used only in SS mode). */
+#define USB_CMD_SDNFW		BIT(8)
+/* Set Test Mode (used only in HS/FS mode). */
+#define USB_CMD_STMODE		BIT(9)
+/* Test mode selector (used only in HS/FS mode) */
+#define USB_STS_TMODE_SEL_MASK	GENMASK(11, 10)
+#define USB_STS_TMODE_SEL(p)	(((p) << 10) & USB_STS_TMODE_SEL_MASK)
+/*
+ *  Send Latency Tolerance Message Device Notification TP (used only
+ *  in SS mode).
+ */
+#define USB_CMD_SDNLTM		BIT(12)
+/* Send Custom Transaction Packet (used only in SS mode) */
+#define USB_CMD_SPKT		BIT(13)
+/*Device Notification 'Function Wake' - Interface value (only in SS mode. */
+#define USB_CMD_DNFW_INT_MASK	GENMASK(23, 16)
+#define USB_STS_DNFW_INT(p)	(((p) << 16) & USB_CMD_DNFW_INT_MASK)
+/*
+ * Device Notification 'Latency Tolerance Message' -373 BELT value [7:0]
+ * (used only in SS mode).
+ */
+#define USB_CMD_DNLTM_BELT_MASK	GENMASK(27, 16)
+#define USB_STS_DNLTM_BELT(p)	(((p) << 16) & USB_CMD_DNLTM_BELT_MASK)
+
+/* USB_ITPN - bitmasks */
+/*
+ * ITP(SS) / SOF (HS/FS) number
+ * In SS mode this field represent number of last ITP received from host.
+ * In HS/FS mode this field represent number of last SOF received from host.
+ */
+#define USB_ITPN_MASK		GENMASK(13, 0)
+#define USB_ITPN(p)		((p) & USB_ITPN_MASK)
+
+/* USB_LPM - bitmasks */
+/* Host Initiated Resume Duration. */
+#define USB_LPM_HIRD_MASK	GENMASK(3, 0)
+#define USB_LPM_HIRD(p)		((p) & USB_LPM_HIRD_MASK)
+/* Remote Wakeup Enable (bRemoteWake). */
+#define USB_LPM_BRW		BIT(4)
+
+/* USB_IEN - bitmasks */
+/* SS connection interrupt enable */
+#define USB_IEN_CONIEN		BIT(0)
+/* SS disconnection interrupt enable. */
+#define USB_IEN_DISIEN		BIT(1)
+/* USB SS warm reset interrupt enable. */
+#define USB_IEN_UWRESIEN	BIT(2)
+/* USB SS hot reset interrupt enable */
+#define USB_IEN_UHRESIEN	BIT(3)
+/* SS link U3 state enter interrupt enable (suspend).*/
+#define USB_IEN_U3ENTIEN	BIT(4)
+/* SS link U3 state exit interrupt enable (wakeup). */
+#define USB_IEN_U3EXTIEN	BIT(5)
+/* SS link U2 state enter interrupt enable.*/
+#define USB_IEN_U2ENTIEN	BIT(6)
+/* SS link U2 state exit interrupt enable.*/
+#define USB_IEN_U2EXTIEN	BIT(7)
+/* SS link U1 state enter interrupt enable.*/
+#define USB_IEN_U1ENTIEN	BIT(8)
+/* SS link U1 state exit interrupt enable.*/
+#define USB_IEN_U1EXTIEN	BIT(9)
+/* ITP/SOF packet detected interrupt enable.*/
+#define USB_IEN_ITPIEN		BIT(10)
+/* Wakeup interrupt enable.*/
+#define USB_IEN_WAKEIEN		BIT(11)
+/* Send Custom Packet interrupt enable.*/
+#define USB_IEN_SPKTIEN		BIT(12)
+/* HS/FS mode connection interrupt enable.*/
+#define USB_IEN_CON2IEN		BIT(16)
+/* HS/FS mode disconnection interrupt enable.*/
+#define USB_IEN_DIS2IEN		BIT(17)
+/* USB reset (HS/FS mode) interrupt enable.*/
+#define USB_IEN_U2RESIEN	BIT(18)
+/* LPM L2 state enter interrupt enable.*/
+#define USB_IEN_L2ENTIEN	BIT(20)
+/* LPM  L2 state exit interrupt enable.*/
+#define USB_IEN_L2EXTIEN	BIT(21)
+/* LPM L1 state enter interrupt enable.*/
+#define USB_IEN_L1ENTIEN	BIT(24)
+/* LPM  L1 state exit interrupt enable.*/
+#define USB_IEN_L1EXTIEN	BIT(25)
+/* Configuration reset interrupt enable.*/
+#define USB_IEN_CFGRESIEN	BIT(26)
+/* Start of the USB SS warm reset interrupt enable.*/
+#define USB_IEN_UWRESSIEN	BIT(28)
+/* End of the USB SS warm reset interrupt enable.*/
+#define USB_IEN_UWRESEIEN	BIT(29)
+
+#define USB_IEN_INIT  (USB_IEN_U2RESIEN | USB_ISTS_DIS2I | USB_IEN_CON2IEN \
+		       | USB_IEN_UHRESIEN | USB_IEN_UWRESIEN | USB_IEN_DISIEN \
+		       | USB_IEN_CONIEN | USB_IEN_U3EXTIEN | USB_IEN_L2ENTIEN \
+		       | USB_IEN_L2EXTIEN)
+
+/* USB_ISTS - bitmasks */
+/* SS Connection detected. */
+#define USB_ISTS_CONI		BIT(0)
+/* SS Disconnection detected. */
+#define USB_ISTS_DISI		BIT(1)
+/* UUSB warm reset detectede. */
+#define USB_ISTS_UWRESI		BIT(2)
+/* USB hot reset detected. */
+#define USB_ISTS_UHRESI		BIT(3)
+/* U3 link state enter detected (suspend).*/
+#define USB_ISTS_U3ENTI		BIT(4)
+/* U3 link state exit detected (wakeup). */
+#define USB_ISTS_U3EXTI		BIT(5)
+/* U2 link state enter detected.*/
+#define USB_ISTS_U2ENTI		BIT(6)
+/* U2 link state exit detected.*/
+#define USB_ISTS_U2EXTI		BIT(7)
+/* U1 link state enter detected.*/
+#define USB_ISTS_U1ENTI		BIT(8)
+/* U1 link state exit detected.*/
+#define USB_ISTS_U1EXTI		BIT(9)
+/* ITP/SOF packet detected.*/
+#define USB_ISTS_ITPI		BIT(10)
+/* Wakeup detected.*/
+#define USB_ISTS_WAKEI		BIT(11)
+/* Send Custom Packet detected.*/
+#define USB_ISTS_SPKTI		BIT(12)
+/* HS/FS mode connection detected.*/
+#define USB_ISTS_CON2I		BIT(16)
+/* HS/FS mode disconnection detected.*/
+#define USB_ISTS_DIS2I		BIT(17)
+/* USB reset (HS/FS mode) detected.*/
+#define USB_ISTS_U2RESI		BIT(18)
+/* LPM L2 state enter detected.*/
+#define USB_ISTS_L2ENTI		BIT(20)
+/* LPM  L2 state exit detected.*/
+#define USB_ISTS_L2EXTI		BIT(21)
+/* LPM L1 state enter detected.*/
+#define USB_ISTS_L1ENTI		BIT(24)
+/* LPM L1 state exit detected.*/
+#define USB_ISTS_L1EXTI		BIT(25)
+/* USB configuration reset detected.*/
+#define USB_ISTS_CFGRESI	BIT(26)
+/* Start of the USB warm reset detected.*/
+#define USB_ISTS_UWRESSI	BIT(28)
+/* End of the USB warm reset detected.*/
+#define USB_ISTS_UWRESEI	BIT(29)
+
+/* USB_SEL - bitmasks */
+#define EP_SEL_EPNO_MASK	GENMASK(3, 0)
+/* Endpoint number. */
+#define EP_SEL_EPNO(p)		((p) & EP_SEL_EPNO_MASK)
+/* Endpoint direction bit - 0 - OUT, 1 - IN. */
+#define EP_SEL_DIR		BIT(7)
+
+#define select_ep_in(nr)	(EP_SEL_EPNO(p) | EP_SEL_DIR)
+#define select_ep_out		(EP_SEL_EPNO(p))
+
+/* EP_TRADDR - bitmasks */
+/* Transfer Ring address. */
+#define EP_TRADDR_TRADDR(p)	((p))
+
+/* EP_CFG - bitmasks */
+/* Endpoint enable */
+#define EP_CFG_ENABLE		BIT(0)
+/*
+ *  Endpoint type.
+ * 1 - isochronous
+ * 2 - bulk
+ * 3 - interrupt
+ */
+#define EP_CFG_EPTYPE_MASK	GENMASK(2, 1)
+#define EP_CFG_EPTYPE(p)	(((p) << 1)  & EP_CFG_EPTYPE_MASK)
+/* Stream support enable (only in SS mode). */
+#define EP_CFG_STREAM_EN	BIT(3)
+/* TDL check (only in SS mode for BULK EP). */
+#define EP_CFG_TDL_CHK		BIT(4)
+/* SID check (only in SS mode for BULK OUT EP). */
+#define EP_CFG_SID_CHK		BIT(5)
+/* DMA transfer endianness. */
+#define EP_CFG_EPENDIAN		BIT(7)
+/* Max burst size (used only in SS mode). */
+#define EP_CFG_MAXBURST_MASK	GENMASK(11, 8)
+#define EP_CFG_MAXBURST(p)	(((p) << 8) & EP_CFG_MAXBURST_MASK)
+/* ISO max burst. */
+#define EP_CFG_MULT_MASK	GENMASK(15, 14)
+#define EP_CFG_MULT(p)		(((p) << 14) & EP_CFG_MULT)
+/* ISO max burst. */
+#define EP_CFG_MAXPKTSIZE_MASK	GENMASK(26, 16)
+#define EP_CFG_MAXPKTSIZE(p)	(((p) << 16) & EP_CFG_MAXPKTSIZE_MASK)
+/* Max number of buffered packets. */
+#define EP_CFG_BUFFERING_MASK	GENMASK(31, 27)
+#define EP_CFG_BUFFERING(p)	(((p) << 27) & EP_CFG_BUFFERING_MASK)
+
+/* EP_CMD - bitmasks */
+/* Endpoint reset. */
+#define EP_CMD_EPRST		BIT(0)
+/* Endpoint STALL set. */
+#define EP_CMD_SSTALL		BIT(1)
+/* Endpoint STALL clear. */
+#define EP_CMD_CSTALL		BIT(2)
+/* Send ERDY TP. */
+#define EP_CMD_ERDY		BIT(3)
+/* Request complete. */
+#define EP_CMD_REQ_CMPL		BIT(5)
+/* Transfer descriptor ready. */
+#define EP_CMD_DRDY		BIT(6)
+/* Data flush. */
+#define EP_CMD_DFLUSH		BIT(7)
+/*
+ * Transfer Descriptor Length write  (used only for Bulk Stream capable
+ * endpoints in SS mode).
+ */
+#define EP_CMD_STDL		BIT(8)
+/* Transfer Descriptor Length (used only in SS mode for bulk endpoints). */
+#define EP_CMD_TDL_MASK		GENMASK(15, 9)
+#define EP_CMD_TDL(p)		(((p) << 9) & EP_CMD_TDL_MASK)
+/* ERDY Stream ID value (used in SS mode). */
+#define EP_CMD_ERDY_SID_MASK	GENMASK(31, 16)
+#define EP_CMD_ERDY_SID(p)	(((p) << 16) & EP_CMD_SID_MASK)
+
+/* EP_STS - bitmasks */
+/* Setup transfer complete. */
+#define EP_STS_SETUP		BIT(0)
+/* Endpoint STALL status. */
+#define EP_STS_STALL(p)		((p) & BIT(1))
+/* Interrupt On Complete. */
+#define EP_STS_IOC		BIT(2)
+/* Interrupt on Short Packet. */
+#define EP_STS_ISP		BIT(3)
+/* Transfer descriptor missing. */
+#define EP_STS_DESCMIS		BIT(4)
+/* Stream Rejected (used only in SS mode) */
+#define EP_STS_STREAMR		BIT(5)
+/* EXIT from MOVE DATA State (used only for stream transfers in SS mode). */
+#define EP_STS_MD_EXIT		BIT(6)
+/* TRB error. */
+#define EP_STS_TRBERR		BIT(7)
+/* Not ready (used only in SS mode). */
+#define EP_STS_NRDY		BIT(8)
+/* DMA busy. */
+#define EP_STS_DBUSY(p)		((p) & BIT(9))
+/* Endpoint Buffer Empty */
+#define EP_STS_BUFFEMPTY(p)	((p) & BIT(10))
+/* Current Cycle Status */
+#define EP_STS_CCS(p)		((p) & BIT(11))
+/* Prime (used only in SS mode. */
+#define EP_STS_PRIME		BIT(12)
+/* Stream error (used only in SS mode). */
+#define EP_STS_SIDERR		BIT(13)
+/* OUT size mismatch. */
+#define EP_STS_OUTSMM		BIT(14)
+/* ISO transmission error. */
+#define EP_STS_ISOERR		BIT(15)
+/* Host Packet Pending (only for SS mode). */
+#define EP_STS_HOSTPP(p)	((p) & BIT(16))
+/* Stream Protocol State Machine State (only for Bulk stream endpoints). */
+#define EP_STS_SPSMST_MASK		GENMASK(18, 17)
+#define EP_STS_SPSMST_DISABLED(p)	(((p) & EP_STS_SPSMST_MASK) >> 17)
+#define EP_STS_SPSMST_IDLE(p)		(((p) & EP_STS_SPSMST_MASK) >> 17)
+#define EP_STS_SPSMST_START_STREAM(p)	(((p) & EP_STS_SPSMST_MASK) >> 17)
+#define EP_STS_SPSMST_MOVE_DATA(p)	(((p) & EP_STS_SPSMST_MASK) >> 17)
+/* Interrupt On Transfer complete. */
+#define EP_STS_IOT		BIT(19)
+/* OUT queue endpoint number. */
+#define EP_STS_OUTQ_NO_MASK	GENMASK(27, 24)
+#define EP_STS_OUTQ_NO(p)	(((p) & EP_STS_OUTQ_NO_MASK) >> 24)
+/* OUT queue valid flag. */
+#define EP_STS_OUTQ_VAL_MASK	BIT(28)
+#define EP_STS_OUTQ_VAL(p)	((p) & EP_STS_OUTQ_VAL_MASK)
+/* SETUP WAIT. */
+#define EP_STS_STPWAIT		BIT(31)
+
+/* EP_STS_SID - bitmasks */
+/* Stream ID (used only in SS mode). */
+#define EP_STS_SID_MASK		GENMASK(15, 0)
+#define EP_STS_SID(p)		((p) & EP_STS_SID_MASK)
+
+/* EP_STS_EN - bitmasks */
+/* SETUP interrupt enable. */
+#define EP_STS_EN_SETUPEN	BIT(0)
+/* OUT transfer missing descriptor enable. */
+#define EP_STS_EN_DESCMISEN	BIT(4)
+/* Stream Rejected enable. */
+#define EP_STS_EN_STREAMREN	BIT(5)
+/* Move Data Exit enable.*/
+#define EP_STS_EN_MD_EXITEN	BIT(6)
+/* TRB enable. */
+#define EP_STS_EN_TRBERREN	BIT(7)
+/* NRDY enable. */
+#define EP_STS_EN_NRDYEN	BIT(8)
+/* Prime enable. */
+#define EP_STS_EN_PRIMEEEN	BIT(12)
+/* Stream error enable. */
+#define EP_STS_EN_SIDERREN	BIT(13)
+/* OUT size mismatch enable. */
+#define EP_STS_EN_OUTSMMEN	BIT(14)
+/* ISO transmission error enable. */
+#define EP_STS_EN_ISOERREN	BIT(15)
+/* Interrupt on Transmission complete enable. */
+#define EP_STS_EN_IOTEN		BIT(19)
+/* Setup Wait interrupt enable. */
+#define EP_STS_EN_STPWAITEN	BIT(31)
+
+/* DRBL- bitmasks */
+#define DB_VALUE_BY_INDEX(index) (1 << (index))
+#define DB_VALUE_EP0_OUT	BIT(0)
+#define DB_VALUE_EP0_IN		BIT(16)
+
+/* EP_IEN - bitmasks */
+#define EP_IEN(index)		(1 << (index))
+#define EP_IEN_EP_OUT0		BIT(0)
+#define EP_IEN_EP_IN0		BIT(16)
+
+/* EP_ISTS - bitmasks */
+#define EP_ISTS(index)		(1 << (index))
+#define EP_ISTS_EP_OUT0		BIT(0)
+#define EP_ISTS_EP_IN0		BIT(16)
+
+/* EP_PWR- bitmasks */
+/*Power Shut Off capability enable*/
+#define PUSB_PWR_PSO_EN		BIT(0)
+/*Power Shut Off capability disable*/
+#define PUSB_PWR_PSO_DS		BIT(1)
+/*
+ * Enables turning-off Reference Clock.
+ * This bit is optional and implemented only when support for OTG is
+ * implemented (indicated by OTG_READY bit set to '1').
+ */
+#define PUSB_PWR_STB_CLK_SWITCH_EN	BIT(8)
+/*
+ * Status bit indicating that operation required by STB_CLK_SWITCH_EN write
+ * is completed
+ */
+#define PUSB_PWR_STB_CLK_SWITCH_DONE	BIT(9)
+/* This bit informs if Fast Registers Access is enabled. */
+#define PUSB_PWR_FST_REG_ACCESS_STAT	BIT(30)
+/* Fast Registers Access Enable. */
+#define PUSB_PWR_FST_REG_ACCESS	BIT(31)
+
+/* USB_CAP1- bitmasks */
+/*
+ * SFR Interface type
+ * These field reflects type of SFR interface implemented:
+ * 0x0 - OCP
+ * 0x1 - AHB,
+ * 0x2 - PLB
+ * 0x3 - AXI
+ * 0x4-0xF - reserved
+ */
+#define USB_CAP1_SFR_TYPE_MASK	GENMASK(3, 0)
+#define DEV_SFR_TYPE_OCP(p)	(((p) & USB_CAP1_SFR_TYPE_MASK) == 0x0)
+#define DEV_SFR_TYPE_AHB(p)	(((p) & USB_CAP1_SFR_TYPE_MASK) == 0x1)
+#define DEV_SFR_TYPE_PLB(p)	(((p) & USB_CAP1_SFR_TYPE_MASK) == 0x2)
+#define DEV_SFR_TYPE_AXI(p)	(((p) & USB_CAP1_SFR_TYPE_MASK) == 0x3)
+/*
+ * SFR Interface width
+ * These field reflects width of SFR interface implemented:
+ * 0x0 - 8 bit interface,
+ * 0x1 - 16 bit interface,
+ * 0x2 - 32 bit interface
+ * 0x3 - 64 bit interface
+ * 0x4-0xF - reserved
+ */
+#define USB_CAP1_SFR_WIDTH_MASK	GENMASK(7, 4)
+#define DEV_SFR_WIDTH_8(p)	(((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x0 << 4))
+#define DEV_SFR_WIDTH_16(p)	(((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x1 << 4))
+#define DEV_SFR_WIDTH_32(p)	(((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x2 << 4))
+#define DEV_SFR_WIDTH_64(p)	(((p) & USB_CAP1_SFR_WIDTH_MASK) == (0x3 << 4))
+/*
+ * DMA Interface type
+ * These field reflects type of DMA interface implemented:
+ * 0x0 - OCP
+ * 0x1 - AHB,
+ * 0x2 - PLB
+ * 0x3 - AXI
+ * 0x4-0xF - reserved
+ */
+#define USB_CAP1_DMA_TYPE_MASK	GENMASK(11, 8)
+#define DEV_DMA_TYPE_OCP(p)	(((p) & USB_CAP1_DMA_TYPE_MASK) == (0x0 << 8))
+#define DEV_DMA_TYPE_AHB(p)	(((p) & USB_CAP1_DMA_TYPE_MASK) == (0x1 << 8))
+#define DEV_DMA_TYPE_PLB(p)	(((p) & USB_CAP1_DMA_TYPE_MASK) == (0x2 << 8))
+#define DEV_DMA_TYPE_AXI(p)	(((p) & USB_CAP1_DMA_TYPE_MASK) == (0x3 << 8))
+/*
+ * DMA Interface width
+ * These field reflects width of DMA interface implemented:
+ * 0x0 - reserved,
+ * 0x1 - reserved,
+ * 0x2 - 32 bit interface
+ * 0x3 - 64 bit interface
+ * 0x4-0xF - reserved
+ */
+#define USB_CAP1_DMA_WIDTH_MASK	GENMASK(15, 12)
+#define DEV_DMA_WIDTH_32(p)	(((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x2 << 12))
+#define DEV_DMA_WIDTH_64(p)	(((p) & USB_CAP1_DMA_WIDTH_MASK) == (0x3 << 12))
+/*
+ * USB3 PHY Interface type
+ * These field reflects type of USB3 PHY interface implemented:
+ * 0x0 - USB PIPE,
+ * 0x1 - RMMI,
+ * 0x2-0xF - reserved
+ */
+#define USB_CAP1_U3PHY_TYPE_MASK GENMASK(19, 16)
+#define DEV_U3PHY_PIPE(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x0 << 16))
+#define DEV_U3PHY_RMMI(p) (((p) & USB_CAP1_U3PHY_TYPE_MASK) == (0x1 << 16))
+/*
+ * USB3 PHY Interface width
+ * These field reflects width of USB3 PHY interface implemented:
+ * 0x0 - 8 bit PIPE interface,
+ * 0x1 - 16 bit PIPE interface,
+ * 0x2 - 32 bit PIPE interface,
+ * 0x3 - 64 bit PIPE interface
+ * 0x4-0xF - reserved
+ * Note: When SSIC interface is implemented this field shows the width of
+ * internal PIPE interface. The RMMI interface is always 20bit wide.
+ */
+#define USB_CAP1_U3PHY_WIDTH_MASK GENMASK(23, 20)
+#define DEV_U3PHY_WIDTH_8(p) \
+	(((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x0 << 20))
+#define DEV_U3PHY_WIDTH_16(p) \
+	(((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x1 << 16))
+#define DEV_U3PHY_WIDTH_32(p) \
+	(((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x2 << 20))
+#define DEV_U3PHY_WIDTH_64(p) \
+	(((p) & USB_CAP1_U3PHY_WIDTH_MASK) == (0x3 << 16))
+
+/*
+ * USB2 PHY Interface enable
+ * These field informs if USB2 PHY interface is implemented:
+ * 0x0 - interface NOT implemented,
+ * 0x1 - interface implemented
+ */
+#define USB_CAP1_U2PHY_EN(p)	((p) & BIT(24))
+/*
+ * USB2 PHY Interface type
+ * These field reflects type of USB2 PHY interface implemented:
+ * 0x0 - UTMI,
+ * 0x1 - ULPI
+ */
+#define DEV_U2PHY_ULPI(p)	((p) & BIT(25))
+/*
+ * USB2 PHY Interface width
+ * These field reflects width of USB2 PHY interface implemented:
+ * 0x0 - 8 bit interface,
+ * 0x1 - 16 bit interface,
+ * Note: The ULPI interface is always 8bit wide.
+ */
+#define DEV_U2PHY_WIDTH_16(p)	((p) & BIT(26))
+/*
+ * OTG Ready
+ * 0x0 - pure device mode
+ * 0x1 - some features and ports for CDNS USB OTG controller are implemented.
+ */
+#define USB_CAP1_OTG_READY(p)	((p) & BIT(27))
+
+/* USB_CAP2- bitmasks */
+/*
+ * The actual size of the connected On-chip RAM memory in kB:
+ * - 0 means 256 kB (max supported mem size)
+ * - value other than 0 reflects the mem size in kB
+ */
+#define USB_CAP2_ACTUAL_MEM_SIZE(p) ((p) & GENMASK(7, 0))
+/*
+ * Max supported mem size
+ * These field reflects width of on-chip RAM address bus width,
+ * which determines max supported mem size:
+ * 0x0-0x7 - reserved,
+ * 0x8 - support for 4kB mem,
+ * 0x9 - support for 8kB mem,
+ * 0xA - support for 16kB mem,
+ * 0xB - support for 32kB mem,
+ * 0xC - support for 64kB mem,
+ * 0xD - support for 128kB mem,
+ * 0xE - support for 256kB mem,
+ * 0xF - reserved
+ */
+#define USB_CAP2_MAX_MEM_SIZE(p) ((p) & GENMASK(11, 8))
+
+/* USB_CAP3- bitmasks */
+#define EP_IS_IMPLEMENTED(reg, index) ((reg) & (1 << (index)))
+
+/* USB_CAP4- bitmasks */
+#define EP_SUPPORT_ISO(reg, index) ((reg) & (1 << (index)))
+
+/* USB_CAP5- bitmasks */
+#define EP_SUPPORT_STREAM(reg, index) ((reg) & (1 << (index)))
+
+/* USB_CAP6- bitmasks */
+/* The USBSS-DEV Controller  Internal build number. */
+#define GET_DEV_VERSION__INTERNAL_NUMBER(p) ((p) & GENMASK(7, 0))
+/* The USBSS-DEV Controller  version number. */
+#define GET_DEV_VERSION(p) ((p) & GENMASK(31, 8))
+
+/* DBG_LINK1- bitmasks */
+/*
+ * LFPS_MIN_DET_U1_EXIT value This parameter configures the minimum
+ * time required for decoding the received LFPS as an LFPS.U1_Exit.
+ */
+#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT(p)	((p) & GENMASK(7, 0))
+/*
+ * LFPS_MIN_GEN_U1_EXIT value This parameter configures the minimum time for
+ * phytxelecidle deassertion when LFPS.U1_Exit
+ */
+#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(p)	(((p) << 8) & GENMASK(15, 8))
+/*
+ * RXDET_BREAK_DIS value This parameter configures terminating the Far-end
+ * Receiver termination detection sequence:
+ * 0: it is possible that USBSS_DEV will terminate Farend receiver
+ *    termination detection sequence
+ * 1: USBSS_DEV will not terminate Far-end receiver termination
+ *    detection sequence
+ */
+#define DBG_LINK1_RXDET_BREAK_DIS		BIT(16)
+/* LFPS_GEN_PING value This parameter configures the LFPS.Ping generation */
+#define DBG_LINK1_LFPS_GEN_PING(p)		(((p) << 17) & GENMASK(21, 17))
+/*
+ * Set the LFPS_MIN_DET_U1_EXIT value Writing '1' to this bit writes the
+ * LFPS_MIN_DET_U1_EXIT field value to the device. This bit is automatically
+ * cleared. Writing '0' has no effect
+ */
+#define DBG_LINK1_LFPS_MIN_DET_U1_EXIT_SET	BIT(24)
+/*
+ * Set the LFPS_MIN_GEN_U1_EXIT value. Writing '1' to this bit writes the
+ * LFPS_MIN_GEN_U1_EXIT field value to the device. This bit is automatically
+ * cleared. Writing '0' has no effect
+ */
+#define DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_SET	BIT(25)
+/*
+ * Set the RXDET_BREAK_DIS value Writing '1' to this bit writes
+ * the RXDET_BREAK_DIS field value to the device. This bit is automatically
+ * cleared. Writing '0' has no effect
+ */
+#define DBG_LINK1_RXDET_BREAK_DIS_SET		BIT(26)
+/*
+ * Set the LFPS_GEN_PING_SET value Writing '1' to this bit writes
+ * the LFPS_GEN_PING field value to the device. This bit is automatically
+ * cleared. Writing '0' has no effect."
+ */
+#define DBG_LINK1_LFPS_GEN_PING_SET		BIT(27)
+
+#define gadget_to_cdns3_device(g) (container_of(g, struct cdns3_device, gadget))
+
+#define ep_to_cdns3_ep(ep) (container_of(ep, struct cdns3_endpoint, endpoint))
+
+/*-------------------------------------------------------------------------*/
+/*
+ * USBSS-DEV DMA interface .
+ */
+#define TRBS_PER_SEGMENT	32
+
+struct cdns3_trb {
+	__le32 buffer;
+	__le32 length;
+	__le32 control;
+};
+
+#define TRB_SIZE		(sizeof(struct cdns3_trb))
+#define TRB_RIGN_SIZE		(TRB_SIZE * TRBS_PER_SEGMENT)
+
+/* TRB bit mask */
+#define TRB_TYPE_BITMASK	GENMASK(15, 10)
+#define TRB_TYPE(p)		((p) << 10)
+#define TRB_FIELD_TO_TYPE(p)	(((p) & TRB_TYPE_BITMASK) >> 10)
+
+/* TRB type IDs */
+/* bulk, interrupt, isoc , and control data stage */
+#define TRB_NORMAL		1
+/* TRB for linking ring segments */
+#define TRB_LINK		6
+
+/* Cycle bit - indicates TRB ownership by driver or hw*/
+#define TRB_CYCLE			BIT(0)
+/*
+ * When set to '1', the device will toggle its interpretation of the Cycle bit
+ */
+#define TRB_TOGGLE			BIT(1)
+
+/* Interrupt on short packet*/
+#define TRB_ISP				BIT(2)
+/*Setting this bit enables FIFO DMA operation mode*/
+#define TRB_FIFO_MODE			BIT(3)
+/* Set PCIe no snoop attribute */
+#define TRB_CHAIN			BIT(4)
+/* Interrupt on completion */
+#define TRB_IOC				BIT(5)
+
+/* stream ID bitmasks. */
+#define TRB_STREAM_ID(p)		((p) & GENMASK(31, 16))
+
+/* transfer_len bitmasks. */
+#define TRB_LEN(p)			((p) & GENMASK(16, 0))
+
+/* transfer_len bitmasks - bits 31:24 */
+#define TRB_BURST_LEN(p)		((p) & GENMASK(31, 24))
+
+/* Data buffer pointer bitmasks*/
+#define TRB_BUFFER(p)			((p) & GENMASK(31, 0))
+
+/*-------------------------------------------------------------------------*/
+/* Driver numeric constants */
+
+#define DEVICE_ADDRESS_MAX		127
+
+/* Endpoint init values */
+#define ENDPOINT_MAX_PACKET_LIMIT	1024
+#define ENDPOINT_MAX_STREAMS		15
+
+#define ENDPOINT0_MAX_PACKET_LIMIT	512
+
+/* All endpoints except EP0 */
+#define USB_SS_ENDPOINTS_MAX_COUNT	30
+
+#define ADDR_MODULO_8			8
+
+#define ENDPOINT_ZLP_BUF_SIZE		1024
+
+/*-------------------------------------------------------------------------*/
+/* 18KB is the total size, and 2KB is used for EP0 and configuration */
+#define CDNS3_ONCHIP_BUF_SIZE	16	/* KB */
+#define CDNS3_EP_BUF_SIZE	2	/* KB */
+#define CDNS3_UNALIGNED_BUF_SIZE	16384 /* Bytes */
+/*-------------------------------------------------------------------------*/
+/* Used structs */
+
+struct cdns3_device;
+
+struct cdns3_endpoint {
+	struct usb_ep		endpoint;
+	struct list_head	request_list;
+	struct list_head	ep_match_pending_list;
+
+	struct cdns3_trb	*trb_pool;
+	dma_addr_t		trb_pool_dma;
+
+	struct cdns3_device	*cdns3_dev;
+	char			name[20];
+
+#define EP_ENABLED		BIT(0)
+#define EP_STALL		BIT(1)
+#define EP_WEDGE		BIT(2)
+#define EP_TRANSFER_STARTED	BIT(3)
+#define EP_UPDATE_EP_TRBADDR	BIT(4)
+#define EP_PENDING_REQUEST	BIT(5)
+#define EP_USED			BIT(5)
+	u32			flags;
+
+	void			*aligned_buff;
+	dma_addr_t		aligned_dma_addr;
+	u8			dir;
+	u8			num;
+	u8			type;
+
+	int			free_trbs;
+	u8			pcs;
+	u8			ccs;
+	int			enqueue;
+	int			dequeue;
+};
+
+struct cdns3_request {
+	struct usb_request request;
+	struct cdns3_endpoint *priv_ep;
+	struct cdns3_trb *trb;
+	int start_trb;
+	int end_trb;
+	int on_ring:1;
+};
+
+#define to_cdns3_request(r) (container_of(r, struct cdns3_request, request))
+
+struct cdns3_device {
+	struct device			dev;
+	struct cdns3_usb_regs		__iomem *regs;
+
+	struct usb_gadget		gadget;
+	struct usb_gadget_driver	*gadget_driver;
+
+	struct usb_ctrlrequest		*setup;
+	dma_addr_t			setup_dma;
+	dma_addr_t			trb_ep0_dma;
+	struct cdns3_trb		*trb_ep0;
+	void				*zlp_buf;
+
+	struct cdns3_endpoint		*eps[USB_SS_ENDPOINTS_MAX_COUNT];
+	int				ep_nums;
+	/*
+	 * field used for improving performance. It holds the last
+	 * selected endpoint number
+	 */
+	u32				selected_ep;
+	struct usb_request		*ep0_request;
+	int				ep0_data_dir;
+	int				hw_configured_flag;
+	int				wake_up_flag;
+	u16				isoch_delay;
+	/* generic spin-lock for drivers */
+	spinlock_t			lock;
+
+	unsigned			is_connected:1;
+	unsigned			in_standby_mode:1;
+	unsigned			status_completion_no_call:1;
+	unsigned			u1_allowed:1;
+	unsigned			u2_allowed:1;
+
+	u32				usb_ien;
+	u32				ep_ien;
+	int				setup_pending;
+	struct device			*sysdev;
+	/* The device mode is enabled */
+	int				start_gadget;
+	struct list_head		ep_match_list;
+	/* KB */
+	int				onchip_mem_allocated_size;
+	/* Memory is allocated for OUT */
+	int				out_mem_is_allocated:1;
+	struct work_struct		pending_status_wq;
+	struct usb_request		*pending_status_request;
+};
+
+#endif /* __LINUX_CDNS3_GADGET */
-- 
2.17.1


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

* [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
  2018-11-18 10:08 ` [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper Pawel Laszczak
  2018-11-18 10:08 ` [RFC PATCH v2 02/15] usb:cdns3: Device side header file Pawel Laszczak
@ 2018-11-18 10:08 ` Pawel Laszczak
  2018-11-23 10:53   ` Roger Quadros
  2018-12-04 22:41   ` Rob Herring
  2018-11-18 10:09 ` [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code Pawel Laszczak
                   ` (11 subsequent siblings)
  14 siblings, 2 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:08 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Thsi patch aim at documenting USB related dt-bindings for the
Cadence USBSS-DRD controller.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 .../devicetree/bindings/usb/cdns3-usb.txt       | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/cdns3-usb.txt

diff --git a/Documentation/devicetree/bindings/usb/cdns3-usb.txt b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
new file mode 100644
index 000000000000..f9e953f32d47
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
@@ -0,0 +1,17 @@
+Binding for the Cadence USBSS-DRD controller
+
+Required properties:
+  - reg: Physical base address and size of the controller's register area.
+  - compatible: Should contain: "cdns,usb3"
+  - interrupts: Interrupt specifier. Refer to interrupt bindings.
+
+
+Example:
+	cdns3@f3000000 {
+		compatible = "cdns,usb3";
+		interrupts = <USB_IRQ  7 IRQ_TYPE_LEVEL_HIGH>;
+		reg = <0xf3000000 0x10000	//memory area for OTG/DRD registers
+			0xf3010000 0x10000	//memory area for HOST registers
+			0xf3020000 0x10000>;	//memory area for Device registers
+	};
+
-- 
2.17.1


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

* [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (2 preceding siblings ...)
  2018-11-18 10:08 ` [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-23 11:35   ` Roger Quadros
  2018-11-30  7:32   ` Peter Chen
  2018-11-18 10:09 ` [RFC PATCH v2 05/15] usb:cdns3: Added DRD support Pawel Laszczak
                   ` (10 subsequent siblings)
  14 siblings, 2 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds core.c and core.h file that implements initialization
of platform driver and adds function responsible for selecting,
switching and running appropriate Device/Host mode.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/Makefile |   2 +
 drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
 drivers/usb/cdns3/core.h   | 100 +++++++++
 3 files changed, 515 insertions(+)
 create mode 100644 drivers/usb/cdns3/core.c
 create mode 100644 drivers/usb/cdns3/core.h

diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index dcdd62003c6a..02d25b23c5d3 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -1,3 +1,5 @@
+obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
 obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
 
+cdns3-y					:= core.o
 cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
new file mode 100644
index 000000000000..f9055d4da67f
--- /dev/null
+++ b/drivers/usb/cdns3/core.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Driver.
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Peter Chen <peter.chen@nxp.com>
+ *         Pawel Laszczak <pawell@cadence.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+
+#include "gadget.h"
+#include "core.h"
+
+static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
+{
+	WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
+	return cdns->roles[cdns->role];
+}
+
+static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
+{
+	int ret;
+
+	if (role >= CDNS3_ROLE_END)
+		return 0;
+
+	if (!cdns->roles[role])
+		return -ENXIO;
+
+	mutex_lock(&cdns->mutex);
+	cdns->role = role;
+	ret = cdns->roles[role]->start(cdns);
+	mutex_unlock(&cdns->mutex);
+	return ret;
+}
+
+static inline void cdns3_role_stop(struct cdns3 *cdns)
+{
+	enum cdns3_roles role = cdns->role;
+
+	if (role == CDNS3_ROLE_END)
+		return;
+
+	mutex_lock(&cdns->mutex);
+	cdns->roles[role]->stop(cdns);
+	cdns->role = CDNS3_ROLE_END;
+	mutex_unlock(&cdns->mutex);
+}
+
+static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
+{
+	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
+		//TODO: implements selecting device/host mode
+		return CDNS3_ROLE_HOST;
+	}
+	return cdns->roles[CDNS3_ROLE_HOST]
+		? CDNS3_ROLE_HOST
+		: CDNS3_ROLE_GADGET;
+}
+
+/**
+ * cdns3_core_init_role - initialize role of operation
+ * @cdns: Pointer to cdns3 structure
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_core_init_role(struct cdns3 *cdns)
+{
+	struct device *dev = cdns->dev;
+	enum usb_dr_mode dr_mode;
+
+	dr_mode = usb_get_dr_mode(dev);
+	cdns->role = CDNS3_ROLE_END;
+
+	/*
+	 * If driver can't read mode by means of usb_get_dr_mdoe function then
+	 * chooses mode according with Kernel configuration. This setting
+	 * can be restricted later depending on strap pin configuration.
+	 */
+	if (dr_mode == USB_DR_MODE_UNKNOWN) {
+		if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
+		    IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
+			dr_mode = USB_DR_MODE_OTG;
+		else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
+			dr_mode = USB_DR_MODE_HOST;
+		else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
+			dr_mode = USB_DR_MODE_PERIPHERAL;
+	}
+
+	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
+		//TODO: implements host initialization
+	}
+
+	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
+		//TODO: implements device initialization
+	}
+
+	if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
+		dev_err(dev, "no supported roles\n");
+		return -ENODEV;
+	}
+
+	cdns->dr_mode = dr_mode;
+	return 0;
+}
+
+/**
+ * cdns3_irq - interrupt handler for cdns3 core device
+ *
+ * @irq: irq number for cdns3 core device
+ * @data: structure of cdns3
+ *
+ * Returns IRQ_HANDLED or IRQ_NONE
+ */
+static irqreturn_t cdns3_irq(int irq, void *data)
+{
+	struct cdns3 *cdns = data;
+	irqreturn_t ret = IRQ_NONE;
+
+	/* Handle device/host interrupt */
+	if (cdns->role != CDNS3_ROLE_END)
+		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
+
+	return ret;
+}
+
+static void cdns3_remove_roles(struct cdns3 *cdns)
+{
+	//TODO: implements this function
+}
+
+static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
+{
+	enum cdns3_roles current_role;
+	int ret = 0;
+
+	current_role = cdns->role;
+
+	if (role == CDNS3_ROLE_END)
+		return 0;
+
+	dev_dbg(cdns->dev, "Switching role");
+
+	ret = cdns3_role_start(cdns, role);
+	if (ret) {
+		/* Back to current role */
+		dev_err(cdns->dev, "set %d has failed, back to %d\n",
+			role, current_role);
+		ret = cdns3_role_start(cdns, current_role);
+	}
+
+	return ret;
+}
+
+/**
+ * cdns3_role_switch - work queue handler for role switch
+ *
+ * @work: work queue item structure
+ *
+ * Handles below events:
+ * - Role switch for dual-role devices
+ * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
+ */
+static void cdns3_role_switch(struct work_struct *work)
+{
+	enum cdns3_roles role = CDNS3_ROLE_END;
+	struct cdns3 *cdns;
+	bool device, host;
+
+	cdns = container_of(work, struct cdns3, role_switch_wq);
+
+	//TODO: implements this functions.
+	//host = cdns3_is_host(cdns);
+	//device = cdns3_is_device(cdns);
+	host = 1;
+	device = 0;
+
+	if (host)
+		role = CDNS3_ROLE_HOST;
+	else if (device)
+		role = CDNS3_ROLE_GADGET;
+
+	if (cdns->desired_dr_mode == cdns->current_dr_mode &&
+	    cdns->role == role)
+		return;
+
+	pm_runtime_get_sync(cdns->dev);
+	cdns3_role_stop(cdns);
+
+	if (host) {
+		if (cdns->roles[CDNS3_ROLE_HOST])
+			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
+		pm_runtime_put_sync(cdns->dev);
+		return;
+	}
+
+	if (device)
+		cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
+	else
+		cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
+
+	pm_runtime_put_sync(cdns->dev);
+}
+
+/**
+ * cdns3_probe - probe for cdns3 core device
+ * @pdev: Pointer to cdns3 core platform device
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource	*res;
+	struct cdns3 *cdns;
+	void __iomem *regs;
+	int ret;
+
+	cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
+	if (!cdns)
+		return -ENOMEM;
+
+	cdns->dev = dev;
+
+	platform_set_drvdata(pdev, cdns);
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		dev_err(dev, "missing IRQ\n");
+		return -ENODEV;
+	}
+	cdns->irq = res->start;
+
+	/*
+	 * Request memory region
+	 * region-0: xHCI
+	 * region-1: Peripheral
+	 * region-2: OTG registers
+	 */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(dev, res);
+
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+	cdns->xhci_regs = regs;
+	cdns->xhci_res = res;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+	cdns->dev_regs	= regs;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+	cdns->otg_regs = regs;
+
+	mutex_init(&cdns->mutex);
+
+	cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
+	if (IS_ERR(cdns->phy)) {
+		dev_info(dev, "no generic phy found\n");
+		cdns->phy = NULL;
+		/*
+		 * fall through here!
+		 * if no generic phy found, phy init
+		 * should be done under boot!
+		 */
+	} else {
+		phy_init(cdns->phy);
+	}
+
+	ret = cdns3_core_init_role(cdns);
+	if (ret)
+		goto err1;
+
+	INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
+	if (ret)
+		goto err2;
+
+	if (ret)
+		goto err2;
+
+	cdns->role = cdns3_get_role(cdns);
+
+	ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
+			       dev_name(dev), cdns);
+
+	if (ret)
+		goto err2;
+
+	ret = cdns3_role_start(cdns, cdns->role);
+	if (ret) {
+		dev_err(dev, "can't start %s role\n",
+			cdns3_get_current_role_driver(cdns)->name);
+		goto err2;
+	}
+
+	device_set_wakeup_capable(dev, true);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	/*
+	 * The controller needs less time between bus and controller suspend,
+	 * and we also needs a small delay to avoid frequently entering low
+	 * power mode.
+	 */
+	pm_runtime_set_autosuspend_delay(dev, 20);
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_use_autosuspend(dev);
+	dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
+
+	return 0;
+
+err2:
+	cdns3_remove_roles(cdns);
+err1:
+	return ret;
+}
+
+/**
+ * cdns3_remove - unbind drd driver and clean up
+ * @pdev: Pointer to Linux platform device
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static int cdns3_remove(struct platform_device *pdev)
+{
+	struct cdns3 *cdns = platform_get_drvdata(pdev);
+
+	pm_runtime_get_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	pm_runtime_put_noidle(&pdev->dev);
+	cdns3_remove_roles(cdns);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_cdns3_match[] = {
+	{ .compatible = "cdns,usb3" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, of_cdns3_match);
+#endif
+
+#ifdef CONFIG_PM
+
+#ifdef CONFIG_PM_SLEEP
+static int cdns3_suspend(struct device *dev)
+{
+	//TODO: implements this function
+	return 0;
+}
+
+static int cdns3_resume(struct device *dev)
+{
+	//TODO: implements this function
+	return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+static int cdns3_runtime_suspend(struct device *dev)
+{	//TODO: implements this function
+	return 0;
+}
+
+static int cdns3_runtime_resume(struct device *dev)
+{
+	//TODO: implements this function
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops cdns3_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
+	SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
+};
+
+static struct platform_driver cdns3_driver = {
+	.probe		= cdns3_probe,
+	.remove		= cdns3_remove,
+	.driver		= {
+		.name	= "cdns-usb3",
+		.of_match_table	= of_match_ptr(of_cdns3_match),
+		.pm	= &cdns3_pm_ops,
+	},
+};
+
+static int __init cdns3_driver_platform_register(void)
+{
+	return platform_driver_register(&cdns3_driver);
+}
+module_init(cdns3_driver_platform_register);
+
+static void __exit cdns3_driver_platform_unregister(void)
+{
+	platform_driver_unregister(&cdns3_driver);
+}
+module_exit(cdns3_driver_platform_unregister);
+
+MODULE_ALIAS("platform:cdns3");
+MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
new file mode 100644
index 000000000000..7c8204fe4d3d
--- /dev/null
+++ b/drivers/usb/cdns3/core.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence USBSS DRD Driver.
+ *
+ * Copyright (C) 2017 NXP
+ * Copyright (C) 2018 Cadence.
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *          Pawel Laszczak <pawell@cadence.com>
+ */
+#include <linux/usb/otg.h>
+
+#ifndef __LINUX_CDNS3_CORE_H
+#define __LINUX_CDNS3_CORE_H
+
+struct cdns3;
+enum cdns3_roles {
+	CDNS3_ROLE_HOST = 0,
+	CDNS3_ROLE_GADGET,
+	CDNS3_ROLE_END,
+};
+
+/**
+ * struct cdns3_role_driver - host/gadget role driver
+ * @start: start this role
+ * @stop: stop this role
+ * @suspend: suspend callback for this role
+ * @resume: resume callback for this role
+ * @irq: irq handler for this role
+ * @name: role name string (host/gadget)
+ */
+struct cdns3_role_driver {
+	int (*start)(struct cdns3 *cdns);
+	void (*stop)(struct cdns3 *cdns);
+	int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
+	int (*resume)(struct cdns3 *cdns, bool hibernated);
+	irqreturn_t (*irq)(struct cdns3 *cdns);
+	const char *name;
+};
+
+#define CDNS3_NUM_OF_CLKS	5
+/**
+ * struct cdns3 - Representation of Cadence USB3 DRD controller.
+ * @dev: pointer to Cadence device struct
+ * @xhci_regs: pointer to base of xhci registers
+ * @xhci_res: the resource for xhci
+ * @dev_regs: pointer to base of dev registers
+ * @otg_regs: pointer to base of otg registers
+ * @irq: irq number for controller
+ * @roles: array of supported roles for this controller
+ * @role: current role
+ * @host_dev: the child host device pointer for cdns3 core
+ * @gadget_dev: the child gadget device pointer for cdns3 core
+ * @usb: phy for this controller
+ * @role_switch_wq: work queue item for role switch
+ * @in_lpm: the controller in low power mode
+ * @wakeup_int: the wakeup interrupt
+ * @mutex: the mutex for concurrent code at driver
+ * @dr_mode: supported mode of operation it can be only Host, only Device
+ *           or OTG mode that allow to switch between Device and Host mode.
+ *           This field based on hardware configuration and cant't be changed.
+ * @current_dr_role: current mode of operation when in dual-role mode
+ * @desired_dr_role: desired mode of operation when in dual-role mode.
+ *           This value can be changed during runtime.
+ *           Available options depends on  dr_mode:
+ *           dr_mode                 |  desired_dr_role and current_dr_role
+ *           ----------------------------------------------------------------
+ *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
+ *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
+ *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
+ *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
+ *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
+ *
+ *           Desired_dr_role can be changed by means of debugfs.
+ * @root: debugfs root folder pointer
+ */
+struct cdns3 {
+	struct device			*dev;
+	void __iomem			*xhci_regs;
+	struct resource			*xhci_res;
+	struct cdns3_usb_regs __iomem	*dev_regs;
+	struct cdns3_otg_regs		*otg_regs;
+	int irq;
+	struct cdns3_role_driver	*roles[CDNS3_ROLE_END];
+	enum cdns3_roles		role;
+	struct device			*host_dev;
+	struct device			*gadget_dev;
+	struct phy			*phy;
+	struct work_struct		role_switch_wq;
+	int				in_lpm:1;
+	int				wakeup_int:1;
+	/* mutext used in workqueue*/
+	struct mutex			mutex;
+	enum usb_dr_mode		dr_mode;
+	enum usb_dr_mode		current_dr_mode;
+	enum usb_dr_mode		desired_dr_mode;
+	struct dentry			*root;
+};
+
+#endif /* __LINUX_CDNS3_CORE_H */
-- 
2.17.1


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

* [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (3 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-23 14:51   ` Roger Quadros
  2018-12-04  9:18   ` Peter Chen
  2018-11-18 10:09 ` [RFC PATCH v2 06/15] usb:cdns3: Adds Host support Pawel Laszczak
                   ` (9 subsequent siblings)
  14 siblings, 2 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds supports for detecting Host/Device mode.
Controller has additional OTG register that allow
implement even whole OTG functionality.
At this moment patch adds support only for detecting
the appropriate mode based on strap pins and ID pin.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/Makefile |   2 +-
 drivers/usb/cdns3/core.c   |  27 +++--
 drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
 drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
 4 files changed, 372 insertions(+), 8 deletions(-)
 create mode 100644 drivers/usb/cdns3/drd.c
 create mode 100644 drivers/usb/cdns3/drd.h

diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index 02d25b23c5d3..e779b2a2f8eb 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -1,5 +1,5 @@
 obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
 obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
 
-cdns3-y					:= core.o
+cdns3-y					:= core.o drd.o
 cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index f9055d4da67f..dbee4325da7f 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -17,6 +17,7 @@
 
 #include "gadget.h"
 #include "core.h"
+#include "drd.h"
 
 static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
 {
@@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
 static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
 {
 	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
-		//TODO: implements selecting device/host mode
-		return CDNS3_ROLE_HOST;
+		if (cdns3_is_host(cdns))
+			return CDNS3_ROLE_HOST;
+		if (cdns3_is_device(cdns))
+			return CDNS3_ROLE_GADGET;
 	}
 	return cdns->roles[CDNS3_ROLE_HOST]
 		? CDNS3_ROLE_HOST
@@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
 	struct cdns3 *cdns = data;
 	irqreturn_t ret = IRQ_NONE;
 
+	if (cdns->dr_mode == USB_DR_MODE_OTG) {
+		ret = cdns3_drd_irq(cdns);
+		if (ret == IRQ_HANDLED)
+			return ret;
+	}
+
 	/* Handle device/host interrupt */
 	if (cdns->role != CDNS3_ROLE_END)
 		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
@@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
 
 	cdns = container_of(work, struct cdns3, role_switch_wq);
 
-	//TODO: implements this functions.
-	//host = cdns3_is_host(cdns);
-	//device = cdns3_is_device(cdns);
-	host = 1;
-	device = 0;
+	host = cdns3_is_host(cdns);
+	device = cdns3_is_device(cdns);
 
 	if (host)
 		role = CDNS3_ROLE_HOST;
@@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
 	pm_runtime_get_sync(cdns->dev);
 	cdns3_role_stop(cdns);
 
+	if (cdns->desired_dr_mode != cdns->current_dr_mode) {
+		cdns3_drd_update_mode(cdns);
+		host = cdns3_is_host(cdns);
+		device = cdns3_is_device(cdns);
+	}
+
 	if (host) {
 		if (cdns->roles[CDNS3_ROLE_HOST])
 			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
@@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
 	if (ret)
 		goto err2;
 
+	ret = cdns3_drd_init(cdns);
 	if (ret)
 		goto err2;
 
diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
new file mode 100644
index 000000000000..ac741c80e776
--- /dev/null
+++ b/drivers/usb/cdns3/drd.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Driver.
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak <pawell@cadence.com
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/usb/otg.h>
+
+#include "gadget.h"
+#include "drd.h"
+
+/**
+ * cdns3_set_mode - change mode of OTG Core
+ * @cdns: pointer to context structure
+ * @mode: selected mode from cdns_role
+ */
+void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
+{
+	u32 reg;
+
+	cdns->current_dr_mode = mode;
+	switch (mode) {
+	case USB_DR_MODE_PERIPHERAL:
+		dev_info(cdns->dev, "Set controller to Gadget mode\n");
+		writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
+		       &cdns->otg_regs->cmd);
+		break;
+	case USB_DR_MODE_HOST:
+		dev_info(cdns->dev, "Set controller to Host mode\n");
+		writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
+		       &cdns->otg_regs->cmd);
+		break;
+	case USB_DR_MODE_OTG:
+		dev_info(cdns->dev, "Set controller to OTG mode\n");
+		reg = readl(&cdns->otg_regs->ctrl1);
+		reg |= OTGCTRL1_IDPULLUP;
+		writel(reg, &cdns->otg_regs->ctrl1);
+
+		/* wait until valid ID (ID_VALUE) can be sampled (50ms). */
+		mdelay(50);
+		break;
+	default:
+		cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
+		dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
+		return;
+	}
+}
+
+static int cdns3_otg_get_id(struct cdns3 *cdns)
+{
+	int id;
+
+	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
+	dev_dbg(cdns->dev, "OTG ID: %d", id);
+	return id;
+}
+
+int cdns3_is_host(struct cdns3 *cdns)
+{
+	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
+		return 1;
+	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
+		if (!cdns3_otg_get_id(cdns))
+			return 1;
+
+	return 0;
+}
+
+int cdns3_is_device(struct cdns3 *cdns)
+{
+	if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
+		return 1;
+	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
+		if (cdns3_otg_get_id(cdns))
+			return 1;
+
+	return 0;
+}
+
+/**
+ * cdns3_otg_disable_irq - Disable all OTG interrupts
+ * @cdns: Pointer to controller context structure
+ */
+static void cdns3_otg_disable_irq(struct cdns3 *cdns)
+{
+	writel(0, &cdns->otg_regs->ien);
+}
+
+/**
+ * cdns3_otg_enable_irq - enable id and sess_valid interrupts
+ * @cdns: Pointer to controller context structure
+ */
+static void cdns3_otg_enable_irq(struct cdns3 *cdns)
+{
+	writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
+	       OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
+}
+
+/**
+ * cdns3_init_otg_mode - initialize drd controller
+ * @cdns: Pointer to controller context structure
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+static void cdns3_init_otg_mode(struct cdns3 *cdns)
+{
+	cdns3_otg_disable_irq(cdns);
+	/* clear all interrupts */
+	writel(~0, &cdns->otg_regs->ivect);
+
+	cdns3_set_mode(cdns, USB_DR_MODE_OTG);
+
+	cdns3_otg_enable_irq(cdns);
+}
+
+/**
+ * cdns3_drd_update_mode - initialize mode of operation
+ * @cdns: Pointer to controller context structure
+ *
+ * Returns 0 on success otherwise negative errno
+ */
+int cdns3_drd_update_mode(struct cdns3 *cdns)
+{
+	int ret = 0;
+
+	switch (cdns->desired_dr_mode) {
+	case USB_DR_MODE_PERIPHERAL:
+		cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
+		break;
+	case USB_DR_MODE_HOST:
+		cdns3_set_mode(cdns, USB_DR_MODE_HOST);
+		break;
+	case USB_DR_MODE_OTG:
+		cdns3_init_otg_mode(cdns);
+		break;
+	default:
+		dev_err(cdns->dev, "Unsupported mode of operation %d\n",
+			cdns->dr_mode);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+irqreturn_t cdns3_drd_irq(struct cdns3 *cdns)
+{
+	irqreturn_t ret = IRQ_NONE;
+	u32 reg;
+
+	if (cdns->dr_mode != USB_DR_MODE_OTG)
+		return ret;
+
+	reg = readl(&cdns->otg_regs->ivect);
+	if (!reg)
+		return ret;
+
+	if (reg & OTGIEN_ID_CHANGE_INT) {
+		int id = cdns3_otg_get_id(cdns);
+
+		dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
+			cdns3_otg_get_id(cdns));
+
+		if (id)
+			cdns->role = CDNS3_ROLE_GADGET;
+		else
+			cdns->role = CDNS3_ROLE_HOST;
+
+		queue_work(system_freezable_wq, &cdns->role_switch_wq);
+
+		ret = IRQ_HANDLED;
+	}
+
+	writel(~0, &cdns->otg_regs->ivect);
+	return IRQ_HANDLED;
+}
+
+int cdns3_drd_init(struct cdns3 *cdns)
+{
+	enum usb_dr_mode dr_mode;
+	int ret = 0;
+	u32 state;
+
+	state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts));
+
+	dr_mode = cdns->dr_mode;
+	if (state == OTGSTS_STRAP_HOST) {
+		dev_info(cdns->dev, "Controller strapped to HOST\n");
+		dr_mode = USB_DR_MODE_HOST;
+		if (cdns->dr_mode != USB_DR_MODE_HOST &&
+		    cdns->dr_mode != USB_DR_MODE_OTG)
+			ret = -EINVAL;
+	} else if (state == OTGSTS_STRAP_GADGET) {
+		dev_info(cdns->dev, "Controller strapped to PERIPHERAL\n");
+		dr_mode = USB_DR_MODE_PERIPHERAL;
+		if (cdns->dr_mode != USB_DR_MODE_PERIPHERAL &&
+		    cdns->dr_mode != USB_DR_MODE_OTG)
+			ret = -EINVAL;
+	}
+
+	if (ret) {
+		dev_err(cdns->dev, "Incorrect DRD configuration\n");
+		return ret;
+	}
+
+	//Updating DR mode according to strap.
+	cdns->dr_mode = dr_mode;
+	cdns->desired_dr_mode = dr_mode;
+	cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
+
+	dev_info(cdns->dev, "Controller Device ID: %08lx, Revision ID: %08lx\n",
+		 CDNS_RID(readl(&cdns->otg_regs->rid)),
+		 CDNS_DID(readl(&cdns->otg_regs->did)));
+
+	state = readl(&cdns->otg_regs->sts);
+	if (OTGSTS_OTG_NRDY(state) != 0) {
+		dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
+		return -ENODEV;
+	}
+
+	ret = cdns3_drd_update_mode(cdns);
+
+	return ret;
+}
diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
new file mode 100644
index 000000000000..0faa7520ecac
--- /dev/null
+++ b/drivers/usb/cdns3/drd.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence USB3 DRD part of USBSS driver
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak <pawell@cadence.com>
+ */
+#ifndef __LINUX_CDNS3_DRD
+#define __LINUX_CDNS3_DRD
+
+#include <linux/usb/otg.h>
+#include <linux/phy/phy.h>
+#include "core.h"
+
+/*  DRD register interface. */
+struct cdns3_otg_regs {
+	__le32 did;
+	__le32 rid;
+	__le32 capabilities;
+	__le32 reserved1;
+	__le32 cmd;
+	__le32 sts;
+	__le32 state;
+	__le32 reserved2;
+	__le32 ien;
+	__le32 ivect;
+	__le32 refclk;
+	__le32 tmr;
+	__le32 reserved3[4];
+	__le32 simulate;
+	__le32 override;
+	__le32 susp_ctrl;
+	__le32 reserved4;
+	__le32 anasts;
+	__le32 adp_ramp_time;
+	__le32 ctrl1;
+	__le32 ctrl2;
+};
+
+/* CDNS_RID - bitmasks */
+#define CDNS_RID(p)			((p) & GENMASK(15, 0))
+
+/* CDNS_VID - bitmasks */
+#define CDNS_DID(p)			((p) & GENMASK(31, 0))
+
+/* OTGCMD - bitmasks */
+/* "Request the bus for Device mode. */
+#define OTGCMD_DEV_BUS_REQ	BIT(0)
+/* Request the bus for Host mode */
+#define OTGCMD_HOST_BUS_REQ		BIT(1)
+/* Enable OTG mode. */
+#define OTGCMD_OTG_EN			BIT(2)
+/* Disable OTG mode */
+#define OTGCMD_OTG_DIS			BIT(3)
+/*"Configure OTG as A-Device. */
+#define OTGCMD_A_DEV_EN			BIT(4)
+/*"Configure OTG as A-Device. */
+#define OTGCMD_A_DEV_DIS		BIT(5)
+/* Drop the bus for Device mod	e. */
+#define OTGCMD_DEV_BUS_DROP		BIT(8)
+/* Drop the bus for Host mode*/
+#define OTGCMD_HOST_BUS_DROP		BIT(9)
+/* Power Down USBSS-DEV. */
+#define OTGCMD_DEV_POWER_OFF		BIT(11)
+/* Power Down CDNSXHCI. */
+#define OTGCMD_HOST_POWER_OFF		BIT(12)
+
+/* OTGIEN - bitmasks */
+/* ID change interrupt enable */
+#define OTGIEN_ID_CHANGE_INT		BIT(0)
+/* Vbusvalid fall detected interrupt enable.*/
+#define OTGIEN_VBUSVALID_RISE_INT	BIT(4)
+/* Vbusvalid fall detected interrupt enable */
+#define OTGIEN_VBUSVALID_FALL_INT	BIT(5)
+
+/* OTGSTS - bitmasks */
+/*
+ * Current value of the ID pin. It is only valid when idpullup in
+ *  OTGCTRL1_TYPE register is set to '1'.
+ */
+#define OTGSTS_ID_VALUE			BIT(0)
+/* Current value of the vbus_valid */
+#define OTGSTS_VBUS_VALID		BIT(1)
+/* Current value of the b_sess_vld */
+#define OTGSTS_SESSION_VALID		BIT(2)
+/*Device mode is active*/
+#define OTGSTS_DEV_ACTIVE		BIT(3)
+/* Host mode is active. */
+#define OTGSTS_HOST_ACTIVE		BIT(4)
+/* OTG Controller not ready. */
+#define OTGSTS_OTG_NRDY_MASK		BIT(11)
+#define OTGSTS_OTG_NRDY(p)		((p) & OTGSTS_OTG_NRDY_MASK)
+/*
+ * Value of the strap pins.
+ * 000 - no default configuration
+ * 010 - Controller initiall configured as Host
+ * 100 - Controller initially configured as Device
+ */
+#define OTGSTS_STRAP(p)			(((p) & GENMASK(14, 12)) >> 12)
+#define OTGSTS_STRAP_NO_DEFAULT_CFG	0x00
+#define OTGSTS_STRAP_HOST_OTG		0x01
+#define OTGSTS_STRAP_HOST		0x02
+#define OTGSTS_STRAP_GADGET		0x04
+/* Host mode is turned on. */
+#define OTGSTSE_XHCI_READYF		BIT(26)
+/* "Device mode is turned on .*/
+#define OTGSTS_DEV_READY		BIT(27)
+
+/* OTGREFCLK - bitmasks */
+#define OTGREFCLK_STB_CLK_SWITCH_EN	BIT(31)
+
+/* OTGCTRL1 - bitmasks */
+#define OTGCTRL1_IDPULLUP		BIT(24)
+
+int cdns3_is_host(struct cdns3 *cdns);
+int cdns3_is_device(struct cdns3 *cdns);
+int cdns3_drd_init(struct cdns3 *cdns);
+int cdns3_drd_update_mode(struct cdns3 *cdns);
+irqreturn_t cdns3_drd_irq(struct cdns3 *cdns);
+
+#endif /* __LINUX_CDNS3_DRD */
-- 
2.17.1


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

* [RFC PATCH v2 06/15] usb:cdns3: Adds Host support
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (4 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 05/15] usb:cdns3: Added DRD support Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-23 14:23   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization Pawel Laszczak
                   ` (8 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds host-export.h and host.c file and mplements functions that
allow to initialize, start and stop XHCI host driver.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/Kconfig       |  10 ++
 drivers/usb/cdns3/Makefile      |   1 +
 drivers/usb/cdns3/core.c        |   7 +-
 drivers/usb/cdns3/host-export.h |  30 ++++
 drivers/usb/cdns3/host.c        | 256 ++++++++++++++++++++++++++++++++
 5 files changed, 302 insertions(+), 2 deletions(-)
 create mode 100644 drivers/usb/cdns3/host-export.h
 create mode 100644 drivers/usb/cdns3/host.c

diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
index eb22a8692991..d92bc3d68eb0 100644
--- a/drivers/usb/cdns3/Kconfig
+++ b/drivers/usb/cdns3/Kconfig
@@ -10,6 +10,16 @@ config USB_CDNS3
 
 if USB_CDNS3
 
+config USB_CDNS3_HOST
+        bool "Cadence USB3 host controller"
+        depends on USB_XHCI_HCD
+        help
+          Say Y here to enable host controller functionality of the
+          cadence driver.
+
+          Host controller is compliance with XHCI so it will use
+          standard XHCI driver.
+
 config USB_CDNS3_PCI_WRAP
 	tristate "PCIe-based Platforms"
 	depends on USB_PCI && ACPI
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index e779b2a2f8eb..976117ba67ff 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
 obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
 
 cdns3-y					:= core.o drd.o
+cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
 cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index dbee4325da7f..4cb820be9ff3 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -17,6 +17,7 @@
 
 #include "gadget.h"
 #include "core.h"
+#include "host-export.h"
 #include "drd.h"
 
 static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
@@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
 	}
 
 	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
-		//TODO: implements host initialization
+		if (cdns3_host_init(cdns))
+			dev_info(dev, "doesn't support host\n");
 	}
 
 	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
@@ -142,7 +144,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
 
 static void cdns3_remove_roles(struct cdns3 *cdns)
 {
-	//TODO: implements this function
+	cdns3_host_remove(cdns);
 }
 
 static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
@@ -410,6 +412,7 @@ static struct platform_driver cdns3_driver = {
 
 static int __init cdns3_driver_platform_register(void)
 {
+	cdns3_host_driver_init();
 	return platform_driver_register(&cdns3_driver);
 }
 module_init(cdns3_driver_platform_register);
diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
new file mode 100644
index 000000000000..f8f3b230b472
--- /dev/null
+++ b/drivers/usb/cdns3/host-export.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence USBSS DRD Driver -Host Export APIs
+ *
+ * Copyright (C) 2017 NXP
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ */
+#ifndef __LINUX_CDNS3_HOST_EXPORT
+#define __LINUX_CDNS3_HOST_EXPORT
+
+#ifdef CONFIG_USB_CDNS3_HOST
+
+int cdns3_host_init(struct cdns3 *cdns);
+void cdns3_host_remove(struct cdns3 *cdns);
+void cdns3_host_driver_init(void);
+
+#else
+
+static inline int cdns3_host_init(struct cdns3 *cdns)
+{
+	return -ENXIO;
+}
+
+static inline void cdns3_host_remove(struct cdns3 *cdns) { }
+static inline void cdns3_host_driver_init(void) {}
+
+#endif /* CONFIG_USB_CDNS3_HOST */
+
+#endif /* __LINUX_CDNS3_HOST_EXPORT */
diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
new file mode 100644
index 000000000000..0dd47976cb28
--- /dev/null
+++ b/drivers/usb/cdns3/host.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Driver - host side
+ *
+ * Copyright (C) 2018 Cadence Design Systems.
+ * Copyright (C) 2018 NXP
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ *	    Pawel Laszczak <pawell@cadence.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/pm_runtime.h>
+#include <linux/usb/of.h>
+
+#include "../host/xhci.h"
+#include "core.h"
+#include "host-export.h"
+
+static struct hc_driver __read_mostly xhci_cdns3_hc_driver;
+
+static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci)
+{
+	/*
+	 * As of now platform drivers don't provide MSI support so we ensure
+	 * here that the generic code does not try to make a pci_dev from our
+	 * dev struct in order to setup MSI
+	 */
+	xhci->quirks |= XHCI_PLAT;
+}
+
+static int xhci_cdns3_setup(struct usb_hcd *hcd)
+{
+	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
+	u32 command;
+	int ret;
+
+	ret = xhci_gen_setup(hcd, xhci_cdns3_quirks);
+	if (ret)
+		return ret;
+
+	/* set usbcmd.EU3S */
+	command = readl(&xhci->op_regs->command);
+	command |= CMD_PM_INDEX;
+	writel(command, &xhci->op_regs->command);
+
+	return 0;
+}
+
+static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
+	.extra_priv_size = sizeof(struct xhci_hcd),
+	.reset = xhci_cdns3_setup,
+};
+
+struct cdns3_host {
+	struct device dev;
+	struct usb_hcd *hcd;
+};
+
+static irqreturn_t cdns3_host_irq(struct cdns3 *cdns)
+{
+	struct device *dev = cdns->host_dev;
+	struct usb_hcd	*hcd;
+
+	if (dev)
+		hcd = dev_get_drvdata(dev);
+	else
+		return IRQ_NONE;
+
+	if (hcd)
+		return usb_hcd_irq(cdns->irq, hcd);
+	else
+		return IRQ_NONE;
+}
+
+static void cdns3_host_release(struct device *dev)
+{
+	struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
+
+	kfree(host);
+}
+
+static int cdns3_host_start(struct cdns3 *cdns)
+{
+	struct cdns3_host *host;
+	struct device *dev;
+	struct device *sysdev;
+	struct xhci_hcd	*xhci;
+	int ret;
+
+	host = kzalloc(sizeof(*host), GFP_KERNEL);
+	if (!host)
+		return -ENOMEM;
+
+	dev = &host->dev;
+	dev->release = cdns3_host_release;
+	dev->parent = cdns->dev;
+	dev_set_name(dev, "xhci-cdns3");
+	cdns->host_dev = dev;
+	ret = device_register(dev);
+	if (ret)
+		goto err1;
+
+	sysdev = cdns->dev;
+	/* Try to set 64-bit DMA first */
+	if (WARN_ON(!sysdev->dma_mask))
+		/* Platform did not initialize dma_mask */
+		ret = dma_coerce_mask_and_coherent(sysdev,
+						   DMA_BIT_MASK(64));
+	else
+		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
+
+	/* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
+	if (ret) {
+		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
+		if (ret)
+			return ret;
+	}
+
+	pm_runtime_set_active(dev);
+	pm_runtime_no_callbacks(dev);
+	pm_runtime_enable(dev);
+	host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
+				     dev_name(dev), NULL);
+	if (!host->hcd) {
+		ret = -ENOMEM;
+		goto err2;
+	}
+
+	host->hcd->regs = cdns->xhci_regs;
+	host->hcd->rsrc_start = cdns->xhci_res->start;
+	host->hcd->rsrc_len = resource_size(cdns->xhci_res);
+
+	device_wakeup_enable(host->hcd->self.controller);
+	xhci = hcd_to_xhci(host->hcd);
+
+	xhci->main_hcd = host->hcd;
+	xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
+					    dev_name(dev), host->hcd);
+	if (!xhci->shared_hcd) {
+		ret = -ENOMEM;
+		goto err3;
+	}
+
+	host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
+	xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
+	ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
+	if (ret)
+		goto err4;
+
+	ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
+	if (ret)
+		goto err5;
+
+	device_set_wakeup_capable(dev, true);
+
+	return 0;
+
+err5:
+	usb_remove_hcd(host->hcd);
+err4:
+	usb_put_hcd(xhci->shared_hcd);
+err3:
+	usb_put_hcd(host->hcd);
+err2:
+	device_del(dev);
+err1:
+	put_device(dev);
+	cdns->host_dev = NULL;
+	return ret;
+}
+
+static void cdns3_host_stop(struct cdns3 *cdns)
+{
+	struct device *dev = cdns->host_dev;
+	struct xhci_hcd	*xhci;
+	struct usb_hcd	*hcd;
+
+	if (dev) {
+		hcd = dev_get_drvdata(dev);
+		xhci = hcd_to_xhci(hcd);
+		usb_remove_hcd(xhci->shared_hcd);
+		usb_remove_hcd(hcd);
+		synchronize_irq(cdns->irq);
+		usb_put_hcd(xhci->shared_hcd);
+		usb_put_hcd(hcd);
+		cdns->host_dev = NULL;
+		pm_runtime_set_suspended(dev);
+		pm_runtime_disable(dev);
+		device_del(dev);
+		put_device(dev);
+	}
+}
+
+#if CONFIG_PM
+static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
+{
+	struct device *dev = cdns->host_dev;
+	struct xhci_hcd	*xhci;
+
+	if (!dev)
+		return 0;
+
+	xhci = hcd_to_xhci(dev_get_drvdata(dev));
+	return xhci_suspend(xhci, do_wakeup);
+}
+
+static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
+{
+	struct device *dev = cdns->host_dev;
+	struct xhci_hcd	*xhci;
+
+	if (!dev)
+		return 0;
+
+	xhci = hcd_to_xhci(dev_get_drvdata(dev));
+	return xhci_resume(xhci, hibernated);
+}
+#endif /* CONFIG_PM */
+
+int cdns3_host_init(struct cdns3 *cdns)
+{
+	struct cdns3_role_driver *rdrv;
+
+	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
+	if (!rdrv)
+		return -ENOMEM;
+
+	rdrv->start	= cdns3_host_start;
+	rdrv->stop	= cdns3_host_stop;
+	rdrv->irq	= cdns3_host_irq;
+#if CONFIG_PM
+	rdrv->suspend	= cdns3_host_suspend;
+	rdrv->resume	= cdns3_host_resume;
+#endif /* CONFIG_PM */
+	rdrv->name	= "host";
+	cdns->roles[CDNS3_ROLE_HOST] = rdrv;
+
+	return 0;
+}
+
+void cdns3_host_remove(struct cdns3 *cdns)
+{
+	cdns3_host_stop(cdns);
+}
+
+void __init cdns3_host_driver_init(void)
+{
+	xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
+}
-- 
2.17.1


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

* [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (5 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 06/15] usb:cdns3: Adds Host support Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-28 11:34   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API Pawel Laszczak
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch implements a set of functions responsible for initialization,
configuration, starting and stopping device mode.
This patch also adds new ep0.c that holds all functions related
to endpoint 0.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/Kconfig         |  10 +
 drivers/usb/cdns3/Makefile        |   1 +
 drivers/usb/cdns3/core.c          |   5 +-
 drivers/usb/cdns3/ep0.c           | 105 ++++++++
 drivers/usb/cdns3/gadget-export.h |  27 +++
 drivers/usb/cdns3/gadget.c        | 390 ++++++++++++++++++++++++++++++
 drivers/usb/cdns3/gadget.h        |   4 +
 7 files changed, 541 insertions(+), 1 deletion(-)
 create mode 100644 drivers/usb/cdns3/ep0.c
 create mode 100644 drivers/usb/cdns3/gadget-export.h
 create mode 100644 drivers/usb/cdns3/gadget.c

diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
index d92bc3d68eb0..b7d71b5c4f60 100644
--- a/drivers/usb/cdns3/Kconfig
+++ b/drivers/usb/cdns3/Kconfig
@@ -10,6 +10,16 @@ config USB_CDNS3
 
 if USB_CDNS3
 
+config USB_CDNS3_GADGET
+        bool "Cadence USB3 device controller"
+        depends on USB_GADGET
+        help
+          Say Y here to enable device controller functionality of the
+          cadence USBSS-DEV driver.
+
+          This controller support FF, HS and SS mode. It doeasn't support
+          LS and SSP mode
+
 config USB_CDNS3_HOST
         bool "Cadence USB3 host controller"
         depends on USB_XHCI_HCD
diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index 976117ba67ff..bea6173bf37f 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -2,5 +2,6 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
 obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
 
 cdns3-y					:= core.o drd.o
+cdns3-$(CONFIG_USB_CDNS3_GADGET)	+= gadget.o ep0.o
 cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
 cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index 4cb820be9ff3..1fa233415901 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -18,6 +18,7 @@
 #include "gadget.h"
 #include "core.h"
 #include "host-export.h"
+#include "gadget-export.h"
 #include "drd.h"
 
 static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
@@ -104,7 +105,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
 	}
 
 	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
-		//TODO: implements device initialization
+		if (cdns3_gadget_init(cdns))
+			dev_info(dev, "doesn't support gadget\n");
 	}
 
 	if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
@@ -144,6 +146,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
 
 static void cdns3_remove_roles(struct cdns3 *cdns)
 {
+	cdns3_gadget_remove(cdns);
 	cdns3_host_remove(cdns);
 }
 
diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
new file mode 100644
index 000000000000..c08d02665f9d
--- /dev/null
+++ b/drivers/usb/cdns3/ep0.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Driver - gadget side.
+ *
+ * Copyright (C) 2018 Cadence Design Systems.
+ * Copyright (C) 2017 NXP
+ *
+ * Authors: Pawel Jez <pjez@cadence.com>,
+ *          Pawel Laszczak <pawell@cadence.com>
+ *	    Peter Chen <peter.chen@nxp.com>
+ */
+
+#include "gadget.h"
+
+static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bmAttributes =	USB_ENDPOINT_XFER_CONTROL,
+};
+
+static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
+{
+	//TODO: Implements this function
+}
+
+/**
+ * cdns3_ep0_config - Configures default endpoint
+ * @priv_dev: extended gadget object
+ *
+ * Functions sets parameters: maximal packet size and enables interrupts
+ */
+void cdns3_ep0_config(struct cdns3_device *priv_dev)
+{
+	struct cdns3_usb_regs __iomem *regs;
+	u32 max_packet_size = 64;
+
+	regs = priv_dev->regs;
+
+	if (priv_dev->gadget.speed == USB_SPEED_SUPER)
+		max_packet_size = 512;
+
+	if (priv_dev->ep0_request) {
+		list_del_init(&priv_dev->ep0_request->list);
+		priv_dev->ep0_request = NULL;
+	}
+
+	priv_dev->gadget.ep0->maxpacket = max_packet_size;
+	cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
+
+	/* init ep out */
+	cdns3_select_ep(priv_dev, USB_DIR_OUT);
+
+	writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
+	       &regs->ep_cfg);
+
+	writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN,
+	       &regs->ep_sts_en);
+
+	/* init ep in */
+	cdns3_select_ep(priv_dev, USB_DIR_IN);
+
+	writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
+	       &regs->ep_cfg);
+
+	writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, &regs->ep_sts_en);
+
+	cdns3_set_register_bit(&regs->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS);
+	cdns3_prepare_setup_packet(priv_dev);
+}
+
+/**
+ * cdns3_init_ep0 Initializes software endpoint 0 of gadget
+ * @cdns3: extended gadget object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+int cdns3_init_ep0(struct cdns3_device *priv_dev)
+{
+	struct cdns3_endpoint *ep0;
+
+	ep0 = devm_kzalloc(&priv_dev->dev, sizeof(struct cdns3_endpoint),
+			   GFP_KERNEL);
+
+	if (!ep0)
+		return -ENOMEM;
+
+	ep0->cdns3_dev = priv_dev;
+	sprintf(ep0->name, "ep0");
+
+	/* fill linux fields */
+	//TODO: implements cdns3_gadget_ep0_ops object
+	//ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
+	ep0->endpoint.maxburst = 1;
+	usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
+	ep0->endpoint.address = 0;
+	ep0->endpoint.caps.type_control = 1;
+	ep0->endpoint.caps.dir_in = 1;
+	ep0->endpoint.caps.dir_out = 1;
+	ep0->endpoint.name = ep0->name;
+	ep0->endpoint.desc = &cdns3_gadget_ep0_desc;
+	priv_dev->gadget.ep0 = &ep0->endpoint;
+	INIT_LIST_HEAD(&ep0->request_list);
+
+	return 0;
+}
diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h
new file mode 100644
index 000000000000..257e5e0eef31
--- /dev/null
+++ b/drivers/usb/cdns3/gadget-export.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence USBSS DRD Driver -Gadget Export APIs
+ *
+ * Copyright (C) 2017 NXP
+ *
+ * Authors: Peter Chen <peter.chen@nxp.com>
+ */
+#ifndef __LINUX_CDNS3_GADGET_EXPORT
+#define __LINUX_CDNS3_GADGET_EXPORT
+
+#ifdef CONFIG_USB_CDNS3_GADGET
+
+int cdns3_gadget_init(struct cdns3 *cdns);
+void cdns3_gadget_remove(struct cdns3 *cdns);
+#else
+
+static inline int cdns3_gadget_init(struct cdns3 *cdns)
+{
+	return -ENXIO;
+}
+
+static inline void cdns3_gadget_remove(struct cdns3 *cdns) { }
+
+#endif
+
+#endif /* __LINUX_CDNS3_GADGET_EXPORT */
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
new file mode 100644
index 000000000000..376b68b13d1b
--- /dev/null
+++ b/drivers/usb/cdns3/gadget.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Driver - gadget side.
+ *
+ * Copyright (C) 2018 Cadence Design Systems.
+ * Copyright (C) 2017 NXP
+ *
+ * Authors: Pawel Jez <pjez@cadence.com>,
+ *          Pawel Laszczak <pawell@cadence.com>
+ *	    Peter Chen <peter.chen@nxp.com>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/usb/gadget.h>
+
+#include "core.h"
+#include "gadget-export.h"
+#include "gadget.h"
+
+/**
+ * cdns3_set_register_bit - set bit in given register.
+ * @ptr: address of device controller register to be read and changed
+ * @mask: bits requested to set
+ */
+void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
+{
+	mask = readl(ptr) | mask;
+	writel(mask, ptr);
+}
+
+/**
+ * select_ep - selects endpoint
+ * @priv_dev:  extended gadget object
+ * @ep: endpoint address
+ */
+void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
+{
+	if (priv_dev->selected_ep == ep)
+		return;
+
+	dev_dbg(&priv_dev->dev, "Ep sel: 0x%02x\n", ep);
+	priv_dev->selected_ep = ep;
+	writel(ep, &priv_dev->regs->ep_sel);
+}
+
+/**
+ * cdns3_irq_handler - irq line interrupt handler
+ * @cdns: cdns3 instance
+ *
+ * Returns IRQ_HANDLED when interrupt raised by USBSS_DEV,
+ * IRQ_NONE when interrupt raised by other device connected
+ * to the irq line
+ */
+static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
+{
+	irqreturn_t ret = IRQ_NONE;
+	//TODO: implements this function
+	return ret;
+}
+
+static void cdns3_gadget_config(struct cdns3_device *priv_dev)
+{
+	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
+
+	cdns3_ep0_config(priv_dev);
+
+	/* enable interrupts for endpoint 0 (in and out) */
+	writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, &regs->ep_ien);
+
+	/* enable generic interrupt*/
+	writel(USB_IEN_INIT, &regs->usb_ien);
+	writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, &regs->usb_conf);
+	writel(USB_CONF_DMULT, &regs->usb_conf);
+	writel(USB_CONF_DEVEN, &regs->usb_conf);
+}
+
+/**
+ * cdns3_init_ep Initializes software endpoints of gadget
+ * @cdns3: extended gadget object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int cdns3_init_ep(struct cdns3_device *priv_dev)
+{
+	u32 ep_enabled_reg, iso_ep_reg;
+	struct cdns3_endpoint *priv_ep;
+	int found_endpoints = 0;
+	int ep_dir, ep_number;
+	u32 ep_mask;
+	int i;
+
+	/* Read it from USB_CAP3 to USB_CAP5 */
+	ep_enabled_reg = readl(&priv_dev->regs->usb_cap3);
+	iso_ep_reg = readl(&priv_dev->regs->usb_cap4);
+
+	dev_dbg(&priv_dev->dev, "Initializing non-zero endpoints\n");
+
+	for (i = 0; i < USB_SS_ENDPOINTS_MAX_COUNT; i++) {
+		ep_number = (i / 2) + 1;
+		ep_dir = i % 2;
+		ep_mask = BIT((16 * ep_dir) + ep_number);
+
+		if (!(ep_enabled_reg & ep_mask))
+			continue;
+
+		priv_ep = devm_kzalloc(&priv_dev->dev, sizeof(*priv_ep),
+				       GFP_KERNEL);
+		if (!priv_ep)
+			return -ENOMEM;
+
+		/* set parent of endpoint object */
+		priv_ep->cdns3_dev = priv_dev;
+		priv_dev->eps[found_endpoints++] = priv_ep;
+
+		snprintf(priv_ep->name, sizeof(priv_ep->name), "ep%d%s",
+			 ep_number, !!ep_dir ? "in" : "out");
+		priv_ep->endpoint.name = priv_ep->name;
+
+		usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
+					   ENDPOINT_MAX_PACKET_LIMIT);
+		priv_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;
+		//TODO: Add implementation of cdns3_gadget_ep_ops
+		//priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
+		if (ep_dir)
+			priv_ep->endpoint.caps.dir_in = 1;
+		else
+			priv_ep->endpoint.caps.dir_out = 1;
+
+		if (iso_ep_reg & ep_mask)
+			priv_ep->endpoint.caps.type_iso = 1;
+
+		priv_ep->endpoint.caps.type_bulk = 1;
+		priv_ep->endpoint.caps.type_int = 1;
+		priv_ep->endpoint.maxburst = CDNS3_EP_BUF_SIZE - 1;
+
+		priv_ep->flags = 0;
+
+		dev_info(&priv_dev->dev, "Initialized  %s support: %s %s\n",
+			 priv_ep->name,
+			 priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "",
+			 priv_ep->endpoint.caps.type_iso ? "ISO" : "");
+
+		list_add_tail(&priv_ep->endpoint.ep_list,
+			      &priv_dev->gadget.ep_list);
+		INIT_LIST_HEAD(&priv_ep->request_list);
+		INIT_LIST_HEAD(&priv_ep->ep_match_pending_list);
+	}
+
+	priv_dev->ep_nums = found_endpoints;
+	return 0;
+}
+
+static void cdns3_gadget_release(struct device *dev)
+{
+	struct cdns3_device *priv_dev;
+
+	priv_dev = container_of(dev, struct cdns3_device, dev);
+	kfree(priv_dev);
+}
+
+static int __cdns3_gadget_init(struct cdns3 *cdns)
+{
+	struct cdns3_device *priv_dev;
+	struct device *dev;
+	int ret;
+
+	priv_dev = kzalloc(sizeof(*priv_dev), GFP_KERNEL);
+	if (!priv_dev)
+		return -ENOMEM;
+
+	dev = &priv_dev->dev;
+	dev->release = cdns3_gadget_release;
+	dev->parent = cdns->dev;
+	dev_set_name(dev, "gadget-cdns3");
+	cdns->gadget_dev = dev;
+
+	priv_dev->sysdev = cdns->dev;
+	ret = device_register(dev);
+	if (ret)
+		goto err1;
+
+	priv_dev->regs = cdns->dev_regs;
+
+	/* fill gadget fields */
+	priv_dev->gadget.max_speed = USB_SPEED_SUPER;
+	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
+	//TODO: Add implementation of cdns3_gadget_ops
+	//priv_dev->gadget.ops = &cdns3_gadget_ops;
+	priv_dev->gadget.name = "usb-ss-gadget";
+	priv_dev->gadget.sg_supported = 1;
+	priv_dev->is_connected = 0;
+
+	spin_lock_init(&priv_dev->lock);
+
+	priv_dev->in_standby_mode = 1;
+
+	/* initialize endpoint container */
+	INIT_LIST_HEAD(&priv_dev->gadget.ep_list);
+	INIT_LIST_HEAD(&priv_dev->ep_match_list);
+
+	ret = cdns3_init_ep0(priv_dev);
+	if (ret) {
+		dev_err(dev, "Failed to create endpoint 0\n");
+		ret = -ENOMEM;
+		goto err2;
+	}
+
+	ret = cdns3_init_ep(priv_dev);
+	if (ret) {
+		dev_err(dev, "Failed to create non zero endpoints\n");
+		ret = -ENOMEM;
+		goto err2;
+	}
+
+	/* allocate memory for default endpoint TRB */
+	priv_dev->trb_ep0 = dma_alloc_coherent(priv_dev->sysdev, 24,
+					       &priv_dev->trb_ep0_dma, GFP_DMA);
+	if (!priv_dev->trb_ep0) {
+		dev_err(dev, "Failed to allocate memory for ep0 TRB\n");
+		ret = -ENOMEM;
+		goto err2;
+	}
+
+	/* allocate memory for setup packet buffer */
+	priv_dev->setup = dma_alloc_coherent(priv_dev->sysdev, 8,
+					     &priv_dev->setup_dma, GFP_DMA);
+	if (!priv_dev->setup) {
+		dev_err(dev, "Failed to allocate memory for SETUP buffer\n");
+		ret = -ENOMEM;
+		goto err3;
+	}
+
+	dev_dbg(dev, "Device Controller version: %08x\n",
+		readl(&priv_dev->regs->usb_cap6));
+	dev_dbg(dev, "USB Capabilities:: %08x\n",
+		readl(&priv_dev->regs->usb_cap1));
+	dev_dbg(dev, "On-Chip memory cnfiguration: %08x\n",
+		readl(&priv_dev->regs->usb_cap2));
+
+	/* add USB gadget device */
+	ret = usb_add_gadget_udc(&priv_dev->dev, &priv_dev->gadget);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register USB device controller\n");
+		goto err4;
+	}
+
+	priv_dev->zlp_buf = kzalloc(ENDPOINT_ZLP_BUF_SIZE, GFP_KERNEL);
+	if (!priv_dev->zlp_buf) {
+		ret = -ENOMEM;
+		goto err4;
+	}
+
+	return 0;
+err4:
+	dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup,
+			  priv_dev->setup_dma);
+err3:
+	dma_free_coherent(priv_dev->sysdev, TRB_SIZE * 2, priv_dev->trb_ep0,
+			  priv_dev->trb_ep0_dma);
+err2:
+	device_del(dev);
+err1:
+	put_device(dev);
+	cdns->gadget_dev = NULL;
+	return ret;
+}
+
+/**
+ * cdns3_gadget_remove: parent must call this to remove UDC
+ *
+ * cdns: cdns3 instance
+ */
+void cdns3_gadget_remove(struct cdns3 *cdns)
+{
+	struct cdns3_device *priv_dev;
+
+	if (!cdns->roles[CDNS3_ROLE_GADGET])
+		return;
+
+	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
+	usb_del_gadget_udc(&priv_dev->gadget);
+	dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup,
+			  priv_dev->setup_dma);
+	dma_free_coherent(priv_dev->sysdev, TRB_SIZE * 2, priv_dev->trb_ep0,
+			  priv_dev->trb_ep0_dma);
+	device_unregister(cdns->gadget_dev);
+	cdns->gadget_dev = NULL;
+	kfree(priv_dev->zlp_buf);
+}
+
+static int cdns3_gadget_start(struct cdns3 *cdns)
+{
+	struct cdns3_device *priv_dev = container_of(cdns->gadget_dev,
+			struct cdns3_device, dev);
+	unsigned long flags;
+
+	pm_runtime_get_sync(cdns->dev);
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	priv_dev->start_gadget = 1;
+
+	if (!priv_dev->gadget_driver) {
+		spin_unlock_irqrestore(&priv_dev->lock, flags);
+		return 0;
+	}
+
+	cdns3_gadget_config(priv_dev);
+	priv_dev->in_standby_mode = 0;
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return 0;
+}
+
+static void __cdns3_gadget_stop(struct cdns3 *cdns)
+{
+	struct cdns3_device *priv_dev;
+	unsigned long flags;
+
+	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
+
+	if (priv_dev->gadget_driver)
+		priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
+
+	usb_gadget_disconnect(&priv_dev->gadget);
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
+
+	/* disable interrupt for device */
+	writel(0, &priv_dev->regs->usb_ien);
+	writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
+	priv_dev->start_gadget = 0;
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+}
+
+static void cdns3_gadget_stop(struct cdns3 *cdns)
+{
+	if (cdns->role == CDNS3_ROLE_GADGET)
+		__cdns3_gadget_stop(cdns);
+
+	pm_runtime_mark_last_busy(cdns->dev);
+	pm_runtime_put_autosuspend(cdns->dev);
+}
+
+static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup)
+{
+	__cdns3_gadget_stop(cdns);
+	return 0;
+}
+
+static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated)
+{
+	struct cdns3_device *priv_dev;
+	unsigned long flags;
+
+	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	priv_dev->start_gadget = 1;
+	if (!priv_dev->gadget_driver) {
+		spin_unlock_irqrestore(&priv_dev->lock, flags);
+		return 0;
+	}
+
+	cdns3_gadget_config(priv_dev);
+	priv_dev->in_standby_mode = 0;
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return 0;
+}
+
+/**
+ * cdns3_gadget_init - initialize device structure
+ *
+ * cdns: cdns3 instance
+ *
+ * This function initializes the gadget.
+ */
+int cdns3_gadget_init(struct cdns3 *cdns)
+{
+	struct cdns3_role_driver *rdrv;
+
+	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
+	if (!rdrv)
+		return -ENOMEM;
+
+	rdrv->start	= cdns3_gadget_start;
+	rdrv->stop	= cdns3_gadget_stop;
+	rdrv->suspend	= cdns3_gadget_suspend;
+	rdrv->resume	= cdns3_gadget_resume;
+	rdrv->irq	= cdns3_irq_handler_thread;
+	rdrv->name	= "gadget";
+	cdns->roles[CDNS3_ROLE_GADGET] = rdrv;
+	return __cdns3_gadget_init(cdns);
+}
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
index 75ca6214e79a..3b0d4d2e4831 100644
--- a/drivers/usb/cdns3/gadget.h
+++ b/drivers/usb/cdns3/gadget.h
@@ -1068,4 +1068,8 @@ struct cdns3_device {
 	struct usb_request		*pending_status_request;
 };
 
+void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
+int cdns3_init_ep0(struct cdns3_device *priv_dev);
+void cdns3_ep0_config(struct cdns3_device *priv_dev);
+void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
 #endif /* __LINUX_CDNS3_GADGET */
-- 
2.17.1


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

* [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (6 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-28 12:22   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 09/15] usb:cdns3: EpX " Pawel Laszczak
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds implementation callback function defined in
usb_gadget_ops object.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/gadget.c | 249 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 247 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 376b68b13d1b..702a05faa664 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -17,6 +17,36 @@
 #include "gadget-export.h"
 #include "gadget.h"
 
+/**
+ * cdns3_handshake - spin reading  until handshake completes or fails
+ * @ptr: address of device controller register to be read
+ * @mask: bits to look at in result of read
+ * @done: value of those bits when handshake succeeds
+ * @usec: timeout in microseconds
+ *
+ * Returns negative errno, or zero on success
+ *
+ * Success happens when the "mask" bits have the specified value (hardware
+ * handshake done). There are two failure modes: "usec" have passed (major
+ * hardware flakeout), or the register reads as all-ones (hardware removed).
+ */
+int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
+{
+	u32	result;
+
+	do {
+		result = readl(ptr);
+		if (result == ~(u32)0)	/* card removed */
+			return -ENODEV;
+		result &= mask;
+		if (result == done)
+			return 0;
+		udelay(1);
+		usec--;
+	} while (usec > 0);
+	return -ETIMEDOUT;
+}
+
 /**
  * cdns3_set_register_bit - set bit in given register.
  * @ptr: address of device controller register to be read and changed
@@ -43,6 +73,25 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
 	writel(ep, &priv_dev->regs->ep_sel);
 }
 
+static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+
+	if (priv_ep->trb_pool) {
+		dma_free_coherent(priv_dev->sysdev,
+				  TRB_RIGN_SIZE,
+				  priv_ep->trb_pool, priv_ep->trb_pool_dma);
+		priv_ep->trb_pool = NULL;
+	}
+
+	if (priv_ep->aligned_buff) {
+		dma_free_coherent(priv_dev->sysdev, CDNS3_UNALIGNED_BUF_SIZE,
+				  priv_ep->aligned_buff,
+				  priv_ep->aligned_dma_addr);
+		priv_ep->aligned_buff = NULL;
+	}
+}
+
 /**
  * cdns3_irq_handler - irq line interrupt handler
  * @cdns: cdns3 instance
@@ -58,6 +107,114 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
 	return ret;
 }
 
+/* Find correct direction for HW endpoint according to description */
+static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
+				   struct cdns3_endpoint *priv_ep)
+{
+	return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) ||
+	       (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc));
+}
+
+static struct cdns3_endpoint *cdns3_find_available_ss_ep(struct cdns3_device *priv_dev,
+							 struct usb_endpoint_descriptor *desc)
+{
+	struct usb_ep *ep;
+	struct cdns3_endpoint *priv_ep;
+
+	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
+		unsigned long num;
+		int ret;
+		/* ep name pattern likes epXin or epXout */
+		char c[2] = {ep->name[2], '\0'};
+
+		ret = kstrtoul(c, 10, &num);
+		if (ret)
+			return ERR_PTR(ret);
+
+		priv_ep = ep_to_cdns3_ep(ep);
+		if (cdns3_ep_dir_is_correct(desc, priv_ep)) {
+			if (!(priv_ep->flags & EP_USED)) {
+				priv_ep->num  = num;
+				priv_ep->flags |= EP_USED;
+				return priv_ep;
+			}
+		}
+	}
+	return ERR_PTR(-ENOENT);
+}
+
+static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
+					    struct usb_endpoint_descriptor *desc,
+					    struct usb_ss_ep_comp_descriptor *comp_desc)
+{
+	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+	struct cdns3_endpoint *priv_ep;
+	unsigned long flags;
+
+	priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
+	if (IS_ERR(priv_ep)) {
+		dev_err(&priv_dev->dev, "no available ep\n");
+		return NULL;
+	}
+
+	dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	priv_ep->endpoint.desc = desc;
+	priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
+	priv_ep->type = usb_endpoint_type(desc);
+
+	list_add_tail(&priv_ep->ep_match_pending_list,
+		      &priv_dev->ep_match_list);
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return &priv_ep->endpoint;
+}
+
+/**
+ * cdns3_gadget_get_frame Returns number of actual ITP frame
+ * @gadget: gadget object
+ *
+ * Returns number of actual ITP frame
+ */
+static int cdns3_gadget_get_frame(struct usb_gadget *gadget)
+{
+	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+
+	return readl(&priv_dev->regs->usb_iptn);
+}
+
+static int cdns3_gadget_wakeup(struct usb_gadget *gadget)
+{
+	return 0;
+}
+
+static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget,
+					int is_selfpowered)
+{
+	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	gadget->is_selfpowered = !!is_selfpowered;
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return 0;
+}
+
+static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on)
+{
+	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+
+	if (!priv_dev->start_gadget)
+		return 0;
+
+	if (is_on)
+		writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf);
+	else
+		writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
+
+	return 0;
+}
+
 static void cdns3_gadget_config(struct cdns3_device *priv_dev)
 {
 	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
@@ -74,6 +231,95 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev)
 	writel(USB_CONF_DEVEN, &regs->usb_conf);
 }
 
+/**
+ * cdns3_gadget_udc_start Gadget start
+ * @gadget: gadget object
+ * @driver: driver which operates on this gadget
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int cdns3_gadget_udc_start(struct usb_gadget *gadget,
+				  struct usb_gadget_driver *driver)
+{
+	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+	unsigned long flags;
+
+	if (priv_dev->gadget_driver) {
+		dev_err(&priv_dev->dev, "%s is already bound to %s\n",
+			priv_dev->gadget.name,
+			priv_dev->gadget_driver->driver.name);
+		return -EBUSY;
+	}
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	priv_dev->gadget_driver = driver;
+	if (!priv_dev->start_gadget)
+		goto unlock;
+
+	cdns3_gadget_config(priv_dev);
+unlock:
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return 0;
+}
+
+/**
+ * cdns3_gadget_udc_stop Stops gadget
+ * @gadget: gadget object
+ *
+ * Returns 0
+ */
+static int cdns3_gadget_udc_stop(struct usb_gadget *gadget)
+{
+	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
+	struct cdns3_endpoint *priv_ep, *temp_ep;
+	u32 bEndpointAddress;
+	struct usb_ep *ep;
+	int ret = 0;
+	int i;
+
+	priv_dev->gadget_driver = NULL;
+	list_for_each_entry_safe(priv_ep, temp_ep, &priv_dev->ep_match_list,
+				 ep_match_pending_list) {
+		list_del(&priv_ep->ep_match_pending_list);
+		priv_ep->flags &= ~EP_USED;
+	}
+
+	priv_dev->onchip_mem_allocated_size = 0;
+	priv_dev->out_mem_is_allocated = 0;
+	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
+
+	for (i = 0; i < priv_dev->ep_nums ; i++)
+		cdns3_free_trb_pool(priv_dev->eps[i]);
+
+	if (!priv_dev->start_gadget)
+		return 0;
+
+	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
+		priv_ep = ep_to_cdns3_ep(ep);
+		bEndpointAddress = priv_ep->num | priv_ep->dir;
+		cdns3_select_ep(priv_dev, bEndpointAddress);
+		writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
+		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
+				      EP_CMD_EPRST, 0, 100);
+	}
+
+	/* disable interrupt for device */
+	writel(0, &priv_dev->regs->usb_ien);
+	writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
+
+	return ret;
+}
+
+static const struct usb_gadget_ops cdns3_gadget_ops = {
+	.get_frame = cdns3_gadget_get_frame,
+	.wakeup = cdns3_gadget_wakeup,
+	.set_selfpowered = cdns3_gadget_set_selfpowered,
+	.pullup = cdns3_gadget_pullup,
+	.udc_start = cdns3_gadget_udc_start,
+	.udc_stop = cdns3_gadget_udc_stop,
+	.match_ep = cdns3_gadget_match_ep,
+};
+
 /**
  * cdns3_init_ep Initializes software endpoints of gadget
  * @cdns3: extended gadget object
@@ -184,8 +430,7 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
 	/* fill gadget fields */
 	priv_dev->gadget.max_speed = USB_SPEED_SUPER;
 	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
-	//TODO: Add implementation of cdns3_gadget_ops
-	//priv_dev->gadget.ops = &cdns3_gadget_ops;
+	priv_dev->gadget.ops = &cdns3_gadget_ops;
 	priv_dev->gadget.name = "usb-ss-gadget";
 	priv_dev->gadget.sg_supported = 1;
 	priv_dev->is_connected = 0;
-- 
2.17.1


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

* [RFC PATCH v2 09/15] usb:cdns3: EpX operations part of the API
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (7 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-28 12:46   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 10/15] usb:cdns3: Ep0 " Pawel Laszczak
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch implements callback functions for non-default endpoints
defined in usb_ep_ops object.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/ep0.c    |  18 ++
 drivers/usb/cdns3/gadget.c | 442 ++++++++++++++++++++++++++++++++++++-
 drivers/usb/cdns3/gadget.h |   3 +
 3 files changed, 461 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
index c08d02665f9d..ca1795467155 100644
--- a/drivers/usb/cdns3/ep0.c
+++ b/drivers/usb/cdns3/ep0.c
@@ -23,6 +23,24 @@ static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
 	//TODO: Implements this function
 }
 
+/**
+ * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint
+ * @ep: endpoint object
+ *
+ * Returns 0
+ */
+int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+
+	dev_dbg(&priv_dev->dev, "Wedge for %s\n", ep->name);
+	cdns3_gadget_ep_set_halt(ep, 1);
+	priv_ep->flags |= EP_WEDGE;
+
+	return 0;
+}
+
 /**
  * cdns3_ep0_config - Configures default endpoint
  * @priv_dev: extended gadget object
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 702a05faa664..1f2a434486dc 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -58,6 +58,19 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
 	writel(mask, ptr);
 }
 
+/**
+ * cdns3_next_request - returns next request from list
+ * @list: list containing requests
+ *
+ * Returns request or NULL if no requests in list
+ */
+struct usb_request *cdns3_next_request(struct list_head *list)
+{
+	if (list_empty(list))
+		return NULL;
+	return list_first_entry(list, struct usb_request, list);
+}
+
 /**
  * select_ep - selects endpoint
  * @priv_dev:  extended gadget object
@@ -73,6 +86,53 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
 	writel(ep, &priv_dev->regs->ep_sel);
 }
 
+/**
+ * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint
+ * @priv_ep:  endpoint object
+ *
+ * Function will return 0 on success or -ENOMEM on allocation error
+ */
+static int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct cdns3_trb *link_trb;
+
+	if (!priv_ep->trb_pool) {
+		priv_ep->trb_pool = dma_zalloc_coherent(priv_dev->sysdev,
+							TRB_RIGN_SIZE,
+							&priv_ep->trb_pool_dma,
+							GFP_DMA);
+		if (!priv_ep->trb_pool)
+			return -ENOMEM;
+	} else {
+		memset(priv_ep->trb_pool, 0, TRB_RIGN_SIZE);
+	}
+
+	if (!priv_ep->aligned_buff) {
+		priv_ep->aligned_buff = dma_alloc_coherent(priv_dev->sysdev,
+							   CDNS3_UNALIGNED_BUF_SIZE,
+							   &priv_ep->aligned_dma_addr,
+							   GFP_DMA);
+		if (!priv_ep->aligned_buff) {
+			dma_free_coherent(priv_dev->sysdev,
+					  TRB_RIGN_SIZE,
+					  priv_ep->trb_pool,
+					  priv_ep->trb_pool_dma);
+			priv_ep->trb_pool = NULL;
+
+			return -ENOMEM;
+		}
+	}
+
+	/* Initialize the last TRB as Link TRB */
+	link_trb = (priv_ep->trb_pool + TRBS_PER_SEGMENT - 1);
+	link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma);
+	link_trb->control = TRB_CYCLE | TRB_TYPE(TRB_LINK) |
+			    TRB_CHAIN | TRB_TOGGLE;
+
+	return 0;
+}
+
 static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
 {
 	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
@@ -92,6 +152,73 @@ static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
 	}
 }
 
+/**
+ * cdns3_data_flush - flush data at onchip buffer
+ * @priv_ep: endpoint object
+ *
+ * Endpoint must be selected before call to this function
+ *
+ * Returns zero on success or negative value on failure
+ */
+static int cdns3_data_flush(struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+
+	writel(EP_CMD_DFLUSH, &priv_dev->regs->ep_cmd);
+
+	/* wait for DFLUSH cleared */
+	return cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 100);
+}
+
+/**
+ * cdns3_ep_stall_flush - Stalls and flushes selected endpoint
+ * @priv_ep: endpoint object
+ *
+ * Endpoint must be selected before call to this function
+ */
+static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+
+	writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL,
+	       &priv_dev->regs->ep_cmd);
+
+	/* wait for DFLUSH cleared */
+	cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 100);
+	priv_ep->flags |= EP_STALL;
+}
+
+/**
+ * cdns3_gadget_giveback - call struct usb_request's ->complete callback
+ * @priv_ep: The endpoint to whom the request belongs to
+ * @priv_req: The request we're giving back
+ * @status: completion code for the request
+ *
+ * Must be called with controller's lock held and interrupts disabled. This
+ * function will unmap @req and call its ->complete() callback to notify upper
+ * layers that it has completed.
+ */
+void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
+			   struct cdns3_request *priv_req,
+			   int status)
+{
+	//TODO: Implements this function.
+}
+
+/**
+ * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware
+ * @priv_ep: endpoint object
+ *
+ * Returns zero on success or negative value on failure
+ */
+int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
+			  struct usb_request *request)
+{
+	//TODO: Implements this function.
+
+	return 0;
+}
+
 /**
  * cdns3_irq_handler - irq line interrupt handler
  * @cdns: cdns3 instance
@@ -170,6 +297,318 @@ static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
 	return &priv_ep->endpoint;
 }
 
+/**
+ * cdns3_gadget_ep_enable Enable endpoint
+ * @ep: endpoint object
+ * @desc: endpoint descriptor
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int cdns3_gadget_ep_enable(struct usb_ep *ep,
+				  const struct usb_endpoint_descriptor *desc)
+{
+	struct cdns3_endpoint *priv_ep;
+	struct cdns3_device *priv_dev;
+	unsigned long flags;
+	int ret;
+	u32 reg;
+
+	priv_ep = ep_to_cdns3_ep(ep);
+	priv_dev = priv_ep->cdns3_dev;
+
+	if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) {
+		dev_err(&priv_dev->dev, "usbss: invalid parameters\n");
+		return -EINVAL;
+	}
+
+	if (!desc->wMaxPacketSize) {
+		dev_err(&priv_dev->dev, "usbss: missing wMaxPacketSize\n");
+		return -EINVAL;
+	}
+
+	if (dev_WARN_ONCE(&priv_dev->dev, priv_ep->flags & EP_ENABLED,
+			  "%s is already enabled\n", priv_ep->name))
+		return 0;
+
+	ret = cdns3_allocate_trb_pool(priv_ep);
+	if (ret)
+		return ret;
+
+	dev_dbg(&priv_dev->dev, "Enabling endpoint: %s\n", ep->name);
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	cdns3_select_ep(priv_dev, desc->bEndpointAddress);
+	writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
+
+	ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
+			      EP_CMD_CSTALL | EP_CMD_EPRST, 0, 100);
+
+	cdns3_set_register_bit(&priv_dev->regs->ep_cfg, EP_CFG_ENABLE);
+
+	ep->desc = desc;
+	priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALL);
+	priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR;
+	priv_ep->enqueue = 0;
+	priv_ep->dequeue = 0;
+	reg = readl(&priv_dev->regs->ep_sts);
+	priv_ep->pcs = !!EP_STS_CCS(reg);
+	priv_ep->ccs = !!EP_STS_CCS(reg);
+	/* one TRB is reserved for link TRB used in DMULT mode*/
+	priv_ep->free_trbs = TRBS_PER_SEGMENT - 1;
+
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return 0;
+}
+
+/**
+ * cdns3_gadget_ep_disable Disable endpoint
+ * @ep: endpoint object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int cdns3_gadget_ep_disable(struct usb_ep *ep)
+{
+	struct cdns3_endpoint *priv_ep;
+	struct cdns3_device *priv_dev;
+	unsigned long flags;
+	int ret = 0;
+	struct usb_request *request;
+	u32 ep_cfg;
+
+	if (!ep) {
+		pr_debug("usbss: invalid parameters\n");
+		return -EINVAL;
+	}
+
+	priv_ep = ep_to_cdns3_ep(ep);
+	priv_dev = priv_ep->cdns3_dev;
+
+	if (dev_WARN_ONCE(&priv_dev->dev, !(priv_ep->flags & EP_ENABLED),
+			  "%s is already disabled\n", priv_ep->name))
+		return 0;
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	if (!priv_dev->start_gadget) {
+		dev_dbg(&priv_dev->dev,
+			"Disabling endpoint at disconnection: %s\n", ep->name);
+		spin_unlock_irqrestore(&priv_dev->lock, flags);
+		return 0;
+	}
+
+	dev_dbg(&priv_dev->dev, "Disabling endpoint: %s\n", ep->name);
+
+	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
+	ret = cdns3_data_flush(priv_ep);
+	while (!list_empty(&priv_ep->request_list)) {
+		request = cdns3_next_request(&priv_ep->request_list);
+
+		cdns3_gadget_giveback(priv_ep, to_cdns3_request(request),
+				      -ESHUTDOWN);
+	}
+
+	ep_cfg = readl(&priv_dev->regs->ep_cfg);
+	ep_cfg &= ~EP_CFG_ENABLE;
+	writel(ep_cfg, &priv_dev->regs->ep_cfg);
+	ep->desc = NULL;
+	priv_ep->flags &= ~EP_ENABLED;
+
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+
+	return ret;
+}
+
+/**
+ * cdns3_gadget_ep_alloc_request Allocates request
+ * @ep: endpoint object associated with request
+ * @gfp_flags: gfp flags
+ *
+ * Returns allocated request address, NULL on allocation error
+ */
+struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
+						  gfp_t gfp_flags)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_request *priv_req;
+
+	priv_req = kzalloc(sizeof(*priv_req), gfp_flags);
+	if (!priv_req)
+		return NULL;
+
+	priv_req->priv_ep = priv_ep;
+
+	return &priv_req->request;
+}
+
+/**
+ * cdns3_gadget_ep_free_request Free memory occupied by request
+ * @ep: endpoint object associated with request
+ * @request: request to free memory
+ */
+void cdns3_gadget_ep_free_request(struct usb_ep *ep,
+				  struct usb_request *request)
+{
+	struct cdns3_request *priv_req = to_cdns3_request(request);
+
+	kfree(priv_req);
+}
+
+/**
+ * cdns3_gadget_ep_queue Transfer data on endpoint
+ * @ep: endpoint object
+ * @request: request object
+ * @gfp_flags: gfp flags
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int __cdns3_gadget_ep_queue(struct usb_ep *ep,
+				   struct usb_request *request,
+				   gfp_t gfp_flags)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	int ret = 0;
+
+	request->actual = 0;
+	request->status = -EINPROGRESS;
+
+	dev_dbg(&priv_dev->dev, "Queuing to endpoint: %s\n", priv_ep->name);
+
+	ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
+					    usb_endpoint_dir_in(ep->desc));
+
+	if (ret)
+		return ret;
+
+	if (!cdns3_ep_run_transfer(priv_ep, request))
+		list_add_tail(&request->list, &priv_ep->request_list);
+
+	return ret;
+}
+
+static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
+				 gfp_t gfp_flags)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct usb_request *zlp_request;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags);
+
+	if (ret == 0 && request->zero && request->length &&
+	    (request->length % ep->maxpacket == 0)) {
+		zlp_request = cdns3_gadget_ep_alloc_request(ep, GFP_ATOMIC);
+		zlp_request->buf = priv_dev->zlp_buf;
+		zlp_request->length = 0;
+
+		dev_dbg(&priv_dev->dev, "Queuing ZLP for endpoint: %s\n",
+			priv_ep->name);
+		ret = __cdns3_gadget_ep_queue(ep, zlp_request, gfp_flags);
+	}
+
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return ret;
+}
+
+/**
+ * cdns3_gadget_ep_dequeue Remove request from transfer queue
+ * @ep: endpoint object associated with request
+ * @request: request object
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+int cdns3_gadget_ep_dequeue(struct usb_ep *ep,
+			    struct usb_request *request)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct usb_request *req, *req_temp;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!ep || !request || !ep->desc)
+		return -EINVAL;
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	dev_dbg(&priv_dev->dev, "Dequeue from %s\n", ep->name);
+
+	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
+	if (priv_dev->start_gadget)
+		ret = cdns3_data_flush(priv_ep);
+
+	list_for_each_entry_safe(req, req_temp, &priv_ep->request_list, list) {
+		if (request == req) {
+			cdns3_gadget_giveback(priv_ep,
+					      to_cdns3_request(request),
+					      -ECONNRESET);
+			break;
+		}
+	}
+
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+	return ret;
+}
+
+/**
+ * cdns3_gadget_ep_set_halt Sets/clears stall on selected endpoint
+ * @ep: endpoint object to set/clear stall on
+ * @value: 1 for set stall, 0 for clear stall
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!(priv_ep->flags & EP_ENABLED))
+		return -EPERM;
+
+	/* if actual transfer is pending defer setting stall on this endpoint */
+	if ((priv_ep->flags & EP_PENDING_REQUEST) && value) {
+		priv_ep->flags |= EP_STALL;
+		return 0;
+	}
+
+	dev_dbg(&priv_dev->dev, "Halt endpoint %s\n", priv_ep->name);
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+
+	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
+	if (value) {
+		cdns3_ep_stall_flush(priv_ep);
+	} else {
+		priv_ep->flags &= ~EP_WEDGE;
+		writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
+
+		/* wait for EPRST cleared */
+		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
+				      EP_CMD_EPRST, 0, 100);
+		priv_ep->flags &= ~EP_STALL;
+	}
+
+	priv_ep->flags &= ~EP_PENDING_REQUEST;
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+
+	return ret;
+}
+
+extern const struct usb_ep_ops cdns3_gadget_ep0_ops;
+
+static const struct usb_ep_ops cdns3_gadget_ep_ops = {
+	.enable = cdns3_gadget_ep_enable,
+	.disable = cdns3_gadget_ep_disable,
+	.alloc_request = cdns3_gadget_ep_alloc_request,
+	.free_request = cdns3_gadget_ep_free_request,
+	.queue = cdns3_gadget_ep_queue,
+	.dequeue = cdns3_gadget_ep_dequeue,
+	.set_halt = cdns3_gadget_ep_set_halt,
+	.set_wedge = cdns3_gadget_ep_set_wedge,
+};
+
 /**
  * cdns3_gadget_get_frame Returns number of actual ITP frame
  * @gadget: gadget object
@@ -365,8 +804,7 @@ static int cdns3_init_ep(struct cdns3_device *priv_dev)
 		usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
 					   ENDPOINT_MAX_PACKET_LIMIT);
 		priv_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;
-		//TODO: Add implementation of cdns3_gadget_ep_ops
-		//priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
+		priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
 		if (ep_dir)
 			priv_ep->endpoint.caps.dir_in = 1;
 		else
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
index 3b0d4d2e4831..a4be288b34cb 100644
--- a/drivers/usb/cdns3/gadget.h
+++ b/drivers/usb/cdns3/gadget.h
@@ -1072,4 +1072,7 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
 int cdns3_init_ep0(struct cdns3_device *priv_dev);
 void cdns3_ep0_config(struct cdns3_device *priv_dev);
 void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
+int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
+int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
+
 #endif /* __LINUX_CDNS3_GADGET */
-- 
2.17.1


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

* [RFC PATCH v2 10/15] usb:cdns3: Ep0 operations part of the API
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (8 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 09/15] usb:cdns3: EpX " Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-28 14:31   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality Pawel Laszczak
                   ` (4 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch implements related to default endpoint callback functions
defined in usb_ep_ops object

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/ep0.c    | 191 ++++++++++++++++++++++++++++++++++++-
 drivers/usb/cdns3/gadget.c |   8 ++
 drivers/usb/cdns3/gadget.h |  10 ++
 3 files changed, 207 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
index ca1795467155..d05169e73631 100644
--- a/drivers/usb/cdns3/ep0.c
+++ b/drivers/usb/cdns3/ep0.c
@@ -18,11 +18,185 @@ static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
 	.bmAttributes =	USB_ENDPOINT_XFER_CONTROL,
 };
 
+/**
+ * cdns3_ep0_run_transfer - Do transfer on default endpoint hardware
+ * @priv_dev: extended gadget object
+ * @dma_addr: physical address where data is/will be stored
+ * @length: data length
+ * @erdy: set it to 1 when ERDY packet should be sent -
+ *        exit from flow control state
+ */
+static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
+				   dma_addr_t dma_addr,
+				   unsigned int length, int erdy)
+{
+	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
+
+	priv_dev->trb_ep0->buffer = TRB_BUFFER(dma_addr);
+	priv_dev->trb_ep0->length = TRB_LEN(length);
+	priv_dev->trb_ep0->control = TRB_CYCLE | TRB_IOC | TRB_TYPE(TRB_NORMAL);
+
+	cdns3_select_ep(priv_dev,
+			priv_dev->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT);
+
+	writel(EP_STS_TRBERR, &regs->ep_sts);
+	writel(EP_TRADDR_TRADDR(priv_dev->trb_ep0_dma), &regs->ep_traddr);
+
+	dev_dbg(&priv_dev->dev, "//Ding Dong ep0%s\n",
+		priv_dev->ep0_data_dir ? "IN" : "OUT");
+
+	/* TRB should be prepared before starting transfer */
+	writel(EP_CMD_DRDY, &regs->ep_cmd);
+
+	if (erdy)
+		writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
+}
+
 static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
 {
 	//TODO: Implements this function
 }
 
+static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
+{
+	struct cdns3_endpoint *priv_ep;
+	struct usb_request *request;
+	struct usb_ep *ep;
+	int result = 0;
+
+	if (priv_dev->hw_configured_flag)
+		return;
+
+	writel(USB_CONF_CFGSET, &priv_dev->regs->usb_conf);
+	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
+
+	cdns3_set_register_bit(&priv_dev->regs->usb_conf,
+			       USB_CONF_U1EN | USB_CONF_U2EN);
+
+	/* wait until configuration set */
+	result = cdns3_handshake(&priv_dev->regs->usb_sts,
+				 USB_STS_CFGSTS_MASK, 1, 100);
+
+	priv_dev->hw_configured_flag = 1;
+	cdns3_enable_l1(priv_dev, 1);
+
+	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
+		if (ep->enabled) {
+			priv_ep = ep_to_cdns3_ep(ep);
+			request = cdns3_next_request(&priv_ep->request_list);
+			if (request)
+				cdns3_ep_run_transfer(priv_ep, request);
+		}
+	}
+}
+
+/**
+ * cdns3_gadget_ep0_enable
+ * Function shouldn't be called by gadget driver,
+ * endpoint 0 is allways active
+ */
+static int cdns3_gadget_ep0_enable(struct usb_ep *ep,
+				   const struct usb_endpoint_descriptor *desc)
+{
+	return -EINVAL;
+}
+
+/**
+ * cdns3_gadget_ep0_disable
+ * Function shouldn't be called by gadget driver,
+ * endpoint 0 is allways active
+ */
+static int cdns3_gadget_ep0_disable(struct usb_ep *ep)
+{
+	return -EINVAL;
+}
+
+/**
+ * cdns3_gadget_ep0_set_halt
+ * @ep: pointer to endpoint zero object
+ * @value: 1 for set stall, 0 for clear stall
+ *
+ * Returns 0
+ */
+static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value)
+{
+	/* TODO */
+	return 0;
+}
+
+/**
+ * cdns3_gadget_ep0_queue Transfer data on endpoint zero
+ * @ep: pointer to endpoint zero object
+ * @request: pointer to request object
+ * @gfp_flags: gfp flags
+ *
+ * Returns 0 on success, error code elsewhere
+ */
+static int cdns3_gadget_ep0_queue(struct usb_ep *ep,
+				  struct usb_request *request,
+				  gfp_t gfp_flags)
+{
+	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	unsigned long flags;
+	int erdy_sent = 0;
+	int ret = 0;
+
+	dev_dbg(&priv_dev->dev, "Queue to Ep0%s L: %d\n",
+		priv_dev->ep0_data_dir ? "IN" : "OUT",
+		request->length);
+
+	/* send STATUS stage */
+	if (request->length == 0 && request->zero == 0) {
+		spin_lock_irqsave(&priv_dev->lock, flags);
+		cdns3_select_ep(priv_dev, 0x00);
+
+		erdy_sent = !priv_dev->hw_configured_flag;
+		cdns3_set_hw_configuration(priv_dev);
+
+		if (!erdy_sent)
+			writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL,
+			       &priv_dev->regs->ep_cmd);
+
+		cdns3_prepare_setup_packet(priv_dev);
+		request->actual = 0;
+		priv_dev->status_completion_no_call = true;
+		priv_dev->pending_status_request = request;
+		spin_unlock_irqrestore(&priv_dev->lock, flags);
+
+		/*
+		 * Since there is no completion interrupt for status stage,
+		 * it needs to call ->completion in software after
+		 * ep0_queue is back.
+		 */
+		queue_work(system_freezable_wq, &priv_dev->pending_status_wq);
+		return 0;
+	}
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	if (!list_empty(&priv_ep->request_list)) {
+		dev_err(&priv_dev->dev,
+			"can't handle multiple requests for ep0\n");
+		spin_unlock_irqrestore(&priv_dev->lock, flags);
+		return -EOPNOTSUPP;
+	}
+
+	ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
+					    priv_dev->ep0_data_dir);
+	if (ret) {
+		spin_unlock_irqrestore(&priv_dev->lock, flags);
+		dev_err(&priv_dev->dev, "failed to map request\n");
+		return -EINVAL;
+	}
+
+	priv_dev->ep0_request = request;
+	list_add_tail(&request->list, &priv_ep->request_list);
+	cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1);
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
+
+	return ret;
+}
+
 /**
  * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint
  * @ep: endpoint object
@@ -41,6 +215,17 @@ int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
 	return 0;
 }
 
+const struct usb_ep_ops cdns3_gadget_ep0_ops = {
+	.enable = cdns3_gadget_ep0_enable,
+	.disable = cdns3_gadget_ep0_disable,
+	.alloc_request = cdns3_gadget_ep_alloc_request,
+	.free_request = cdns3_gadget_ep_free_request,
+	.queue = cdns3_gadget_ep0_queue,
+	.dequeue = cdns3_gadget_ep_dequeue,
+	.set_halt = cdns3_gadget_ep0_set_halt,
+	.set_wedge = cdns3_gadget_ep_set_wedge,
+};
+
 /**
  * cdns3_ep0_config - Configures default endpoint
  * @priv_dev: extended gadget object
@@ -62,6 +247,9 @@ void cdns3_ep0_config(struct cdns3_device *priv_dev)
 		priv_dev->ep0_request = NULL;
 	}
 
+	priv_dev->u1_allowed = 0;
+	priv_dev->u2_allowed = 0;
+
 	priv_dev->gadget.ep0->maxpacket = max_packet_size;
 	cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
 
@@ -106,8 +294,7 @@ int cdns3_init_ep0(struct cdns3_device *priv_dev)
 	sprintf(ep0->name, "ep0");
 
 	/* fill linux fields */
-	//TODO: implements cdns3_gadget_ep0_ops object
-	//ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
+	ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
 	ep0->endpoint.maxburst = 1;
 	usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
 	ep0->endpoint.address = 0;
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 1f2a434486dc..c965da16c0c8 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -188,6 +188,14 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
 	priv_ep->flags |= EP_STALL;
 }
 
+void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
+{
+	if (enable)
+		writel(USB_CONF_L1EN, &priv_dev->regs->usb_conf);
+	else
+		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
+}
+
 /**
  * cdns3_gadget_giveback - call struct usb_request's ->complete callback
  * @priv_ep: The endpoint to whom the request belongs to
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
index a4be288b34cb..224f6b830bc9 100644
--- a/drivers/usb/cdns3/gadget.h
+++ b/drivers/usb/cdns3/gadget.h
@@ -1068,11 +1068,21 @@ struct cdns3_device {
 	struct usb_request		*pending_status_request;
 };
 
+int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
 void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
 int cdns3_init_ep0(struct cdns3_device *priv_dev);
 void cdns3_ep0_config(struct cdns3_device *priv_dev);
 void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
+void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
+struct usb_request *cdns3_next_request(struct list_head *list);
+int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
+			  struct usb_request *request);
 int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
 int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
+struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
+						  gfp_t gfp_flags);
+void cdns3_gadget_ep_free_request(struct usb_ep *ep,
+				  struct usb_request *request);
+int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request);
 
 #endif /* __LINUX_CDNS3_GADGET */
-- 
2.17.1


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

* [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (9 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 10/15] usb:cdns3: Ep0 " Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-28 14:54   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function Pawel Laszczak
                   ` (3 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds set of generic functions used for handling interrupts
generated by controller. Interrupt related functions are divided
into three groups. The first is related to ep0 and is placed in ep0.c.
The second is responsible for non-default endpoints and is
implemented in gadget.c file. The last group is not related to
endpoints interrupts and is placed in gadget.c.
All groups have common entry point in cdns3_irq_handler_thread function.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/ep0.c    |  63 +++++++++++
 drivers/usb/cdns3/gadget.c | 224 ++++++++++++++++++++++++++++++++++++-
 drivers/usb/cdns3/gadget.h |   1 +
 3 files changed, 287 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
index d05169e73631..eb92fd234bd7 100644
--- a/drivers/usb/cdns3/ep0.c
+++ b/drivers/usb/cdns3/ep0.c
@@ -90,6 +90,69 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
 	}
 }
 
+static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
+{
+	//TODO: Implements this function
+}
+
+/**
+ * cdns3_ep0_setup_phase - Handling setup USB requests
+ * @priv_dev: extended gadget object
+ */
+static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
+{
+	//TODO: Implements this function.
+}
+
+static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
+{
+	//TODO: Implements this function
+}
+
+/**
+ * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0
+ * @priv_dev: extended gadget object
+ * @dir: 1 for IN direction, 0 for OUT direction
+ */
+void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir)
+{
+	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
+	u32 ep_sts_reg;
+
+	cdns3_select_ep(priv_dev, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT));
+	ep_sts_reg = readl(&regs->ep_sts);
+
+	__pending_setup_status_handler(priv_dev);
+
+	if ((ep_sts_reg & EP_STS_SETUP) && dir == 0) {
+		struct usb_ctrlrequest *setup = priv_dev->setup;
+
+		writel(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
+
+		priv_dev->ep0_data_dir = setup->bRequestType & USB_DIR_IN;
+		cdns3_ep0_setup_phase(priv_dev);
+		ep_sts_reg &= ~(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP);
+	}
+
+	if (ep_sts_reg & EP_STS_TRBERR)
+		writel(EP_STS_TRBERR, &priv_dev->regs->ep_sts);
+
+	if (ep_sts_reg & EP_STS_DESCMIS) {
+		writel(EP_STS_DESCMIS, &priv_dev->regs->ep_sts);
+
+		if (dir == 0 && !priv_dev->setup_pending) {
+			priv_dev->ep0_data_dir = 0;
+			cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
+					       8, 0);
+		}
+	}
+
+	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
+		writel(EP_STS_IOC, &priv_dev->regs->ep_sts);
+		cdns3_transfer_completed(priv_dev);
+	}
+}
+
 /**
  * cdns3_gadget_ep0_enable
  * Function shouldn't be called by gadget driver,
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index c965da16c0c8..309202474e57 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -58,6 +58,18 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
 	writel(mask, ptr);
 }
 
+/**
+ * cdns3_ep_reg_pos_to_index - Macro converts bit position of ep_ists register
+ * to index of endpoint object in cdns3_device.eps[] container
+ * @i: bit position of endpoint for which endpoint object is required
+ *
+ * Remember that endpoint container doesn't contain default endpoint
+ */
+static u8 cdns3_ep_reg_pos_to_index(int i)
+{
+	return ((i / 16) + (((i % 16) - 2) * 2));
+}
+
 /**
  * cdns3_next_request - returns next request from list
  * @list: list containing requests
@@ -188,6 +200,21 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
 	priv_ep->flags |= EP_STALL;
 }
 
+/**
+ * cdns3_gadget_unconfig - reset device configuration
+ * @priv_dev: extended gadget object
+ */
+void cdns3_gadget_unconfig(struct cdns3_device *priv_dev)
+{
+	/* RESET CONFIGURATION */
+	writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf);
+
+	cdns3_enable_l1(priv_dev, 0);
+	priv_dev->hw_configured_flag = 0;
+	priv_dev->onchip_mem_allocated_size = 0;
+	priv_dev->out_mem_is_allocated = 0;
+}
+
 void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
 {
 	if (enable)
@@ -196,6 +223,23 @@ void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
 		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
 }
 
+static enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev)
+{
+	u32 reg;
+
+	reg = readl(&priv_dev->regs->usb_sts);
+
+	if (DEV_SUPERSPEED(reg))
+		return USB_SPEED_SUPER;
+	else if (DEV_HIGHSPEED(reg))
+		return USB_SPEED_HIGH;
+	else if (DEV_FULLSPEED(reg))
+		return USB_SPEED_FULL;
+	else if (DEV_LOWSPEED(reg))
+		return USB_SPEED_LOW;
+	return USB_SPEED_UNKNOWN;
+}
+
 /**
  * cdns3_gadget_giveback - call struct usb_request's ->complete callback
  * @priv_ep: The endpoint to whom the request belongs to
@@ -221,12 +265,136 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
  */
 int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
 			  struct usb_request *request)
+{
+	return 0;
+}
+
+static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
+				     struct cdns3_endpoint *priv_ep)
 {
 	//TODO: Implements this function.
+}
+
+/**
+ * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint
+ * @priv_ep: endpoint object
+ *
+ * Returns 0
+ */
+static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct cdns3_usb_regs __iomem *regs;
+	u32 ep_sts_reg;
+
+	regs = priv_dev->regs;
+
+	cdns3_select_ep(priv_dev, priv_ep->endpoint.address);
+	ep_sts_reg = readl(&regs->ep_sts);
+
+	if (ep_sts_reg & EP_STS_TRBERR)
+		writel(EP_STS_TRBERR, &regs->ep_sts);
+
+	if (ep_sts_reg & EP_STS_ISOERR)
+		writel(EP_STS_ISOERR, &regs->ep_sts);
+
+	if (ep_sts_reg & EP_STS_OUTSMM)
+		writel(EP_STS_OUTSMM, &regs->ep_sts);
+
+	if (ep_sts_reg & EP_STS_NRDY)
+		writel(EP_STS_NRDY, &regs->ep_sts);
+
+	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
+		writel(EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
+		cdns3_transfer_completed(priv_dev, priv_ep);
+	}
+
+	if (ep_sts_reg & EP_STS_DESCMIS)
+		writel(EP_STS_DESCMIS, &regs->ep_sts);
 
 	return 0;
 }
 
+/**
+ * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device
+ * @priv_dev: extended gadget object
+ * @usb_ists: bitmap representation of device's reported interrupts
+ * (usb_ists register value)
+ */
+static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev,
+					      u32 usb_ists)
+{
+	struct cdns3_usb_regs __iomem *regs;
+	int speed = 0;
+
+	regs = priv_dev->regs;
+
+	/* Connection detected */
+	if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) {
+		writel(USB_ISTS_CON2I | USB_ISTS_CONI, &regs->usb_ists);
+		speed = cdns3_get_speed(priv_dev);
+
+		dev_dbg(&priv_dev->dev, "Connection detected at speed: %s %d\n",
+			usb_speed_string(speed), speed);
+
+		priv_dev->gadget.speed = speed;
+		priv_dev->is_connected = 1;
+		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED);
+		cdns3_ep0_config(priv_dev);
+	}
+
+	/* SS Disconnection detected */
+	if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) {
+		dev_dbg(&priv_dev->dev, "Disconnection detected\n");
+
+		writel(USB_ISTS_DIS2I | USB_ISTS_DISI, &regs->usb_ists);
+		if (priv_dev->gadget_driver &&
+		    priv_dev->gadget_driver->disconnect) {
+			spin_unlock(&priv_dev->lock);
+			priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
+			spin_lock(&priv_dev->lock);
+		}
+		priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
+		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED);
+		priv_dev->is_connected = 0;
+		cdns3_gadget_unconfig(priv_dev);
+	}
+
+	if (usb_ists & USB_ISTS_L2ENTI) {
+		dev_dbg(&priv_dev->dev, "Device suspended\n");
+		writel(USB_ISTS_L2ENTI, &regs->usb_ists);
+	}
+
+	/* Exit from standby mode on L2 exit (Suspend in HS/FS or SS) */
+	if (usb_ists & USB_ISTS_L2EXTI) {
+		dev_dbg(&priv_dev->dev, "[Interrupt] L2 exit detected\n");
+		writel(USB_ISTS_L2EXTI, &regs->usb_ists);
+	}
+
+	/* Exit from standby mode on U3 exit (Suspend in HS/FS or SS). */
+	if (usb_ists & USB_ISTS_U3EXTI) {
+		dev_dbg(&priv_dev->dev, "U3 exit detected\n");
+		writel(USB_ISTS_U3EXTI, &regs->usb_ists);
+	}
+
+	/* resets cases */
+	if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) {
+		writel(USB_ISTS_U2RESI | USB_ISTS_UWRESI | USB_ISTS_UHRESI,
+		       &regs->usb_ists);
+
+		/*read again to check the actuall speed*/
+		speed = cdns3_get_speed(priv_dev);
+
+		dev_dbg(&priv_dev->dev, "Reset detected at speed: %s %d\n",
+			usb_speed_string(speed), speed);
+
+		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_DEFAULT);
+		priv_dev->gadget.speed = speed;
+		cdns3_gadget_unconfig(priv_dev);
+		cdns3_ep0_config(priv_dev);
+	}
+}
+
 /**
  * cdns3_irq_handler - irq line interrupt handler
  * @cdns: cdns3 instance
@@ -237,8 +405,62 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
  */
 static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
 {
+	struct cdns3_device *priv_dev;
 	irqreturn_t ret = IRQ_NONE;
-	//TODO: implements this function
+	unsigned long flags;
+	u32 reg;
+
+	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
+	spin_lock_irqsave(&priv_dev->lock, flags);
+
+	/* check USB device interrupt */
+	reg = readl(&priv_dev->regs->usb_ists);
+	if (reg) {
+		dev_dbg(&priv_dev->dev, "IRQ: usb_ists: %08X\n", reg);
+		cdns3_check_usb_interrupt_proceed(priv_dev, reg);
+		ret = IRQ_HANDLED;
+	}
+
+	/* check endpoint interrupt */
+	reg = readl(&priv_dev->regs->ep_ists);
+	if (reg != 0) {
+		dev_dbg(&priv_dev->dev, "IRQ ep_ists: %08X\n", reg);
+	} else {
+		if (USB_STS_CFGSTS(readl(&priv_dev->regs->usb_sts)))
+			ret = IRQ_HANDLED;
+		goto irqend;
+	}
+
+	/* handle default endpoint OUT */
+	if (reg & EP_ISTS_EP_OUT0) {
+		cdns3_check_ep0_interrupt_proceed(priv_dev, 0);
+		ret = IRQ_HANDLED;
+	}
+
+	/* handle default endpoint IN */
+	if (reg & EP_ISTS_EP_IN0) {
+		cdns3_check_ep0_interrupt_proceed(priv_dev, 1);
+		ret = IRQ_HANDLED;
+	}
+
+	/* check if interrupt from non default endpoint, if no exit */
+	reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0);
+	if (!reg)
+		goto irqend;
+
+	do {
+		unsigned int bit_pos = ffs(reg);
+		u32 bit_mask = 1 << (bit_pos - 1);
+		int index;
+
+		index = cdns3_ep_reg_pos_to_index(bit_pos);
+		cdns3_check_ep_interrupt_proceed(priv_dev->eps[index]);
+		reg &= ~bit_mask;
+		ret = IRQ_HANDLED;
+	} while (reg);
+
+irqend:
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
 	return ret;
 }
 
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
index 224f6b830bc9..8c2f363f9340 100644
--- a/drivers/usb/cdns3/gadget.h
+++ b/drivers/usb/cdns3/gadget.h
@@ -1072,6 +1072,7 @@ int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
 void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
 int cdns3_init_ep0(struct cdns3_device *priv_dev);
 void cdns3_ep0_config(struct cdns3_device *priv_dev);
+void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
 void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
 void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
 struct usb_request *cdns3_next_request(struct list_head *list);
-- 
2.17.1


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

* [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (10 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-28 15:50   ` Roger Quadros
  2018-11-18 10:09 ` [RFC PATCH v2 13/15] usb:cdns3: Adds transfer " Pawel Laszczak
                   ` (2 subsequent siblings)
  14 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch implements a set of function related to enumeration process.
Some standard requests are handled on controller driver level and
other are delegated to gadget core driver.
All class requests are delegated to gadget core driver.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/ep0.c    | 491 ++++++++++++++++++++++++++++++++++++-
 drivers/usb/cdns3/gadget.c | 119 +++++++++
 drivers/usb/cdns3/gadget.h |   4 +
 3 files changed, 610 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
index eb92fd234bd7..6f33d98f7684 100644
--- a/drivers/usb/cdns3/ep0.c
+++ b/drivers/usb/cdns3/ep0.c
@@ -10,6 +10,7 @@
  *	    Peter Chen <peter.chen@nxp.com>
  */
 
+#include <linux/usb/composite.h>
 #include "gadget.h"
 
 static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
@@ -52,9 +53,31 @@ static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
 		writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
 }
 
+/**
+ * cdns3_ep0_delegate_req - Returns status of handling setup packet
+ * Setup is handled by gadget driver
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns zero on success or negative value on failure
+ */
+static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev,
+				  struct usb_ctrlrequest *ctrl_req)
+{
+	int ret;
+
+	spin_unlock(&priv_dev->lock);
+	priv_dev->setup_pending = 1;
+	ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req);
+	priv_dev->setup_pending = 0;
+	spin_lock(&priv_dev->lock);
+	return ret;
+}
+
 static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
 {
-	//TODO: Implements this function
+	priv_dev->ep0_data_dir = 0;
+	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 8, 0);
 }
 
 static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
@@ -90,9 +113,431 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
 	}
 }
 
+/**
+ * cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, 0x7FFF on deferred status stage, error code on error
+ */
+static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev,
+					   struct usb_ctrlrequest *ctrl_req)
+{
+	enum usb_device_state device_state = priv_dev->gadget.state;
+	struct cdns3_endpoint *priv_ep, *temp_ep;
+	u32 config = le16_to_cpu(ctrl_req->wValue);
+	int result = 0;
+
+	switch (device_state) {
+	case USB_STATE_ADDRESS:
+		/* Configure non-control EPs */
+		list_for_each_entry_safe(priv_ep, temp_ep,
+					 &priv_dev->ep_match_list,
+					 ep_match_pending_list)
+			cdns3_ep_config(priv_ep);
+
+		result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
+
+		if (result)
+			return result;
+
+		if (config) {
+			cdns3_set_hw_configuration(priv_dev);
+		} else {
+			cdns3_gadget_unconfig(priv_dev);
+			usb_gadget_set_state(&priv_dev->gadget,
+					     USB_STATE_ADDRESS);
+		}
+		break;
+	case USB_STATE_CONFIGURED:
+		result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
+
+		if (!config && !result) {
+			cdns3_gadget_unconfig(priv_dev);
+			usb_gadget_set_state(&priv_dev->gadget,
+					     USB_STATE_ADDRESS);
+		}
+		break;
+	default:
+		result = -EINVAL;
+	}
+
+	return result;
+}
+
+/**
+ * cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev,
+				     struct usb_ctrlrequest *ctrl_req)
+{
+	enum usb_device_state device_state = priv_dev->gadget.state;
+	u32 reg;
+	u32 addr;
+
+	addr = le16_to_cpu(ctrl_req->wValue);
+
+	if (addr > DEVICE_ADDRESS_MAX) {
+		dev_err(&priv_dev->dev,
+			"Device address (%d) cannot be greater than %d\n",
+			addr, DEVICE_ADDRESS_MAX);
+		return -EINVAL;
+	}
+
+	if (device_state == USB_STATE_CONFIGURED) {
+		dev_err(&priv_dev->dev, "USB device already configured\n");
+		return -EINVAL;
+	}
+
+	reg = readl(&priv_dev->regs->usb_cmd);
+
+	writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR,
+	       &priv_dev->regs->usb_cmd);
+
+	usb_gadget_set_state(&priv_dev->gadget,
+			     (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT));
+
+	cdns3_prepare_setup_packet(priv_dev);
+
+	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
+
+	return 0;
+}
+
+/**
+ * cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev,
+				    struct usb_ctrlrequest *ctrl)
+{
+	__le16 *response_pkt;
+	u16 usb_status = 0;
+	u32 recip;
+	u32 reg;
+
+	recip = ctrl->bRequestType & USB_RECIP_MASK;
+
+	switch (recip) {
+	case USB_RECIP_DEVICE:
+		/* self powered */
+		usb_status |= priv_dev->gadget.is_selfpowered;
+
+		if (priv_dev->gadget.speed != USB_SPEED_SUPER)
+			break;
+
+		reg = readl(&priv_dev->regs->usb_sts);
+
+		if (priv_dev->u1_allowed)
+			usb_status |= BIT(USB_DEV_STAT_U1_ENABLED);
+
+		if (priv_dev->u2_allowed)
+			usb_status |= BIT(USB_DEV_STAT_U2_ENABLED);
+
+		if (priv_dev->wake_up_flag)
+			usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP);
+		break;
+	case USB_RECIP_INTERFACE:
+		return cdns3_ep0_delegate_req(priv_dev, ctrl);
+	case USB_RECIP_ENDPOINT:
+		/* check if endpoint is stalled */
+		cdns3_select_ep(priv_dev, ctrl->wIndex);
+		if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)))
+			usb_status =  BIT(USB_ENDPOINT_HALT);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	response_pkt = (__le16 *)priv_dev->setup;
+	*response_pkt = cpu_to_le16(usb_status);
+
+	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
+			       sizeof(*response_pkt), 1);
+	return 0;
+}
+
+static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev,
+					   struct usb_ctrlrequest *ctrl,
+					   int set)
+{
+	enum usb_device_state state;
+	enum usb_device_speed speed;
+	int ret = 0;
+	u32 wValue;
+	u32 wIndex;
+	u16 tmode;
+
+	wValue = le16_to_cpu(ctrl->wValue);
+	wIndex = le16_to_cpu(ctrl->wIndex);
+	state = priv_dev->gadget.state;
+	speed = priv_dev->gadget.speed;
+
+	switch (ctrl->wValue) {
+	case USB_DEVICE_REMOTE_WAKEUP:
+		priv_dev->wake_up_flag = !!set;
+		break;
+	case USB_DEVICE_U1_ENABLE:
+		if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
+			return -EINVAL;
+
+		priv_dev->u1_allowed = !!set;
+		break;
+	case USB_DEVICE_U2_ENABLE:
+		if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
+			return -EINVAL;
+
+		priv_dev->u2_allowed = !!set;
+		break;
+	case USB_DEVICE_LTM_ENABLE:
+		ret = -EINVAL;
+		break;
+	case USB_DEVICE_TEST_MODE:
+		if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH)
+			return -EINVAL;
+
+		tmode = le16_to_cpu(ctrl->wIndex);
+
+		if (!set || (tmode & 0xff) != 0)
+			return -EINVAL;
+
+		switch (tmode >> 8) {
+		case TEST_J:
+		case TEST_K:
+		case TEST_SE0_NAK:
+		case TEST_PACKET:
+			cdns3_set_register_bit(&priv_dev->regs->usb_cmd,
+					       USB_CMD_STMODE |
+					       USB_STS_TMODE_SEL(tmode - 1));
+			break;
+		default:
+			ret = -EINVAL;
+		}
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev,
+					 struct usb_ctrlrequest *ctrl,
+					 int set)
+{
+	u32 wValue;
+	int ret = 0;
+
+	wValue = le16_to_cpu(ctrl->wValue);
+
+	switch (wValue) {
+	case USB_INTRF_FUNC_SUSPEND:
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev,
+					     struct usb_ctrlrequest *ctrl,
+					     int set)
+{
+	struct cdns3_endpoint *priv_ep;
+	int ret = 0;
+	u8 index;
+
+	if (!(ctrl->wIndex &  ~USB_DIR_IN))
+		return 0;
+
+	index = cdns3_ep_addr_to_index(ctrl->wIndex);
+	priv_ep = priv_dev->eps[index];
+
+	cdns3_select_ep(priv_dev, ctrl->wIndex);
+
+	if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT)
+		return -EINVAL;
+
+	if (set) {
+		writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
+		priv_ep->flags |= EP_STALL;
+	} else {
+		struct usb_request *request;
+
+		if (priv_dev->eps[index]->flags & EP_WEDGE) {
+			cdns3_select_ep(priv_dev, 0x00);
+			return 0;
+		}
+
+		writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
+
+		/* wait for EPRST cleared */
+		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
+				      EP_CMD_EPRST, 0, 100);
+		if (ret)
+			return -EINVAL;
+
+		priv_ep->flags &= ~EP_STALL;
+
+		request = cdns3_next_request(&priv_ep->request_list);
+		if (request)
+			cdns3_ep_run_transfer(priv_ep, request);
+	}
+	return ret;
+}
+
+/**
+ * cdns3_req_ep0_handle_feature -
+ * Handling of GET/SET_FEATURE standard USB request
+ *
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ * @set: must be set to 1 for SET_FEATURE request
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev,
+					struct usb_ctrlrequest *ctrl,
+					int set)
+{
+	int ret = 0;
+	u32 recip;
+
+	recip = ctrl->bRequestType & USB_RECIP_MASK;
+
+	switch (recip) {
+	case USB_RECIP_DEVICE:
+		ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set);
+		break;
+	case USB_RECIP_INTERFACE:
+		ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set);
+		break;
+	case USB_RECIP_ENDPOINT:
+		ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (!ret)
+		writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
+
+	return ret;
+}
+
+/**
+ * cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev,
+				 struct usb_ctrlrequest *ctrl_req)
+{
+	if (priv_dev->gadget.state < USB_STATE_ADDRESS)
+		return -EINVAL;
+
+	if (ctrl_req->wLength != 6) {
+		dev_err(&priv_dev->dev, "Set SEL should be 6 bytes, got %d\n",
+			ctrl_req->wLength);
+		return -EINVAL;
+	}
+
+	priv_dev->ep0_data_dir = 0;
+	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1);
+	return 0;
+}
+
+/**
+ * cdns3_req_ep0_set_isoch_delay -
+ * Handling of GET_ISOCH_DELAY standard USB request
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev,
+					 struct usb_ctrlrequest *ctrl_req)
+{
+	if (ctrl_req->wIndex || ctrl_req->wLength)
+		return -EINVAL;
+
+	priv_dev->isoch_delay = ctrl_req->wValue;
+	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
+	return 0;
+}
+
+/**
+ * cdns3_ep0_standard_request - Handling standard USB requests
+ * @priv_dev: extended gadget object
+ * @ctrl_req: pointer to received setup packet
+ *
+ * Returns 0 if success, error code on error
+ */
+static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev,
+				      struct usb_ctrlrequest *ctrl_req)
+{
+	int ret;
+
+	switch (ctrl_req->bRequest) {
+	case USB_REQ_SET_ADDRESS:
+		ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req);
+		break;
+	case USB_REQ_SET_CONFIGURATION:
+		ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req);
+		break;
+	case USB_REQ_GET_STATUS:
+		ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req);
+		break;
+	case USB_REQ_CLEAR_FEATURE:
+		ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0);
+		break;
+	case USB_REQ_SET_FEATURE:
+		ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1);
+		break;
+	case USB_REQ_SET_SEL:
+		ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req);
+		break;
+	case USB_REQ_SET_ISOCH_DELAY:
+		ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req);
+		break;
+	default:
+		ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
+		break;
+	}
+
+	return ret;
+}
+
 static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
 {
-	//TODO: Implements this function
+	struct usb_request *request = priv_dev->pending_status_request;
+
+	if (priv_dev->status_completion_no_call && request &&
+	    request->complete) {
+		request->complete(priv_dev->gadget.ep0, request);
+		priv_dev->status_completion_no_call = 0;
+	}
+}
+
+void cdns3_pending_setup_status_handler(struct work_struct *work)
+{
+	struct cdns3_device *priv_dev = container_of(work, struct cdns3_device,
+			pending_status_wq);
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv_dev->lock, flags);
+	__pending_setup_status_handler(priv_dev);
+	spin_unlock_irqrestore(&priv_dev->lock, flags);
 }
 
 /**
@@ -101,12 +546,50 @@ static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
  */
 static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
 {
-	//TODO: Implements this function.
+	struct usb_ctrlrequest *ctrl = priv_dev->setup;
+	int result;
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
+		result = cdns3_ep0_standard_request(priv_dev, ctrl);
+	else
+		result = cdns3_ep0_delegate_req(priv_dev, ctrl);
+
+	if (result != 0 && result != USB_GADGET_DELAYED_STATUS) {
+		dev_dbg(&priv_dev->dev, "STALL for ep0\n");
+		/* set_stall on ep0 */
+		cdns3_select_ep(priv_dev, 0x00);
+		writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
+		writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
+	}
 }
 
 static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
 {
-	//TODO: Implements this function
+	if (priv_dev->ep0_request) {
+		usb_gadget_unmap_request_by_dev(priv_dev->sysdev,
+						priv_dev->ep0_request,
+						priv_dev->ep0_data_dir);
+
+		priv_dev->ep0_request->actual =
+			TRB_LEN(le32_to_cpu(priv_dev->trb_ep0->length));
+
+		dev_dbg(&priv_dev->dev, "Ep0 completion length %d\n",
+			priv_dev->ep0_request->actual);
+		list_del_init(&priv_dev->ep0_request->list);
+	}
+
+	if (priv_dev->ep0_request &&
+	    priv_dev->ep0_request->complete) {
+		spin_unlock(&priv_dev->lock);
+		priv_dev->ep0_request->complete(priv_dev->gadget.ep0,
+						priv_dev->ep0_request);
+
+		priv_dev->ep0_request = NULL;
+		spin_lock(&priv_dev->lock);
+	}
+
+	cdns3_prepare_setup_packet(priv_dev);
+	writel(EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
 }
 
 /**
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 309202474e57..0202ff5f6c90 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -70,6 +70,30 @@ static u8 cdns3_ep_reg_pos_to_index(int i)
 	return ((i / 16) + (((i % 16) - 2) * 2));
 }
 
+/**
+ * cdns3_ep_addr_to_index - Macro converts endpoint address to
+ * index of endpoint object in cdns3_device.eps[] container
+ * @ep_addr: endpoint address for which endpoint object is required
+ *
+ * Remember that endpoint container doesn't contain default endpoint
+ */
+u8 cdns3_ep_addr_to_index(u8 ep_addr)
+{
+	return (((ep_addr & 0x7F) - 1) + ((ep_addr & USB_DIR_IN) ? 1 : 0));
+}
+
+/**
+ * cdns3_ep_addr_to_bit_pos - Macro converts endpoint address to
+ * bit position in ep_ists register
+ * @ep_addr: endpoint address for which bit position is required
+ *
+ * Remember that endpoint container doesn't contain default endpoint
+ */
+static u32 cdns3_ep_addr_to_bit_pos(u8 ep_addr)
+{
+	return (1 << (ep_addr & 0x7F)) << ((ep_addr & 0x80) ? 16 : 0);
+}
+
 /**
  * cdns3_next_request - returns next request from list
  * @list: list containing requests
@@ -464,6 +488,99 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
 	return ret;
 }
 
+/**
+ * cdns3_ep_onchip_buffer_alloc - Try to allocate onchip buf for EP
+ *
+ * The real allocation will occur during write to EP_CFG register,
+ * this function is used to check if the 'size' allocation is allowed.
+ *
+ * @priv_dev: extended gadget object
+ * @size: the size (KB) for EP would like to allocate
+ * @is_in: the direction for EP
+ *
+ * Return 0 if the later allocation is allowed or negative value on failure
+ */
+static int cdns3_ep_onchip_buffer_alloc(struct cdns3_device *priv_dev,
+					int size, int is_in)
+{
+	if (is_in) {
+		priv_dev->onchip_mem_allocated_size += size;
+	} else if (!priv_dev->out_mem_is_allocated) {
+		 /* ALL OUT EPs are shared the same chunk onchip memory */
+		priv_dev->onchip_mem_allocated_size += size;
+		priv_dev->out_mem_is_allocated = 1;
+	}
+
+	if (priv_dev->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) {
+		priv_dev->onchip_mem_allocated_size -= size;
+		return -EPERM;
+	} else {
+		return 0;
+	}
+}
+
+/**
+ * cdns3_ep_config Configure hardware endpoint
+ * @priv_ep: extended endpoint object
+ */
+void cdns3_ep_config(struct cdns3_endpoint *priv_ep)
+{
+	bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC);
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	u32 bEndpointAddress = priv_ep->num | priv_ep->dir;
+	u32 interrupt_mask = EP_STS_EN_TRBERREN;
+	u32 max_packet_size = 0;
+	u32 ep_cfg = 0;
+	int ret;
+
+	if (priv_ep->type == USB_ENDPOINT_XFER_INT) {
+		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT);
+	} else if (priv_ep->type == USB_ENDPOINT_XFER_BULK) {
+		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK);
+	} else {
+		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC);
+		interrupt_mask = 0xFFFFFFFF;
+	}
+
+	switch (priv_dev->gadget.speed) {
+	case USB_SPEED_FULL:
+		max_packet_size = is_iso_ep ? 1023 : 64;
+		break;
+	case USB_SPEED_HIGH:
+		max_packet_size = is_iso_ep ? 1024 : 512;
+		break;
+	case USB_SPEED_SUPER:
+		max_packet_size = 1024;
+		break;
+	default:
+		//all other speed are not supported
+		return;
+	}
+
+	ret = cdns3_ep_onchip_buffer_alloc(priv_dev, CDNS3_EP_BUF_SIZE,
+					   priv_ep->dir);
+	if (ret) {
+		dev_err(&priv_dev->dev, "onchip mem is full, ep is invalid\n");
+		return;
+	}
+
+	ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) |
+		  EP_CFG_BUFFERING(CDNS3_EP_BUF_SIZE - 1) |
+		  EP_CFG_MAXBURST(priv_ep->endpoint.maxburst);
+
+	cdns3_select_ep(priv_dev, bEndpointAddress);
+
+	writel(ep_cfg, &priv_dev->regs->ep_cfg);
+	writel(interrupt_mask, &priv_dev->regs->ep_sts_en);
+
+	dev_dbg(&priv_dev->dev, "Configure %s: with val %08x\n",
+		priv_ep->name, ep_cfg);
+
+	/* enable interrupt for selected endpoint */
+	cdns3_set_register_bit(&priv_dev->regs->ep_ien,
+			       cdns3_ep_addr_to_bit_pos(bEndpointAddress));
+}
+
 /* Find correct direction for HW endpoint according to description */
 static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
 				   struct cdns3_endpoint *priv_ep)
@@ -1104,6 +1221,8 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
 	priv_dev->is_connected = 0;
 
 	spin_lock_init(&priv_dev->lock);
+	INIT_WORK(&priv_dev->pending_status_wq,
+		  cdns3_pending_setup_status_handler);
 
 	priv_dev->in_standby_mode = 1;
 
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
index 8c2f363f9340..db8c6cb9f2a5 100644
--- a/drivers/usb/cdns3/gadget.h
+++ b/drivers/usb/cdns3/gadget.h
@@ -1070,14 +1070,18 @@ struct cdns3_device {
 
 int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
 void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
+void cdns3_pending_setup_status_handler(struct work_struct *work);
 int cdns3_init_ep0(struct cdns3_device *priv_dev);
 void cdns3_ep0_config(struct cdns3_device *priv_dev);
+void cdns3_ep_config(struct cdns3_endpoint *priv_ep);
 void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
 void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
 void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
 struct usb_request *cdns3_next_request(struct list_head *list);
+void cdns3_gadget_unconfig(struct cdns3_device *priv_dev);
 int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
 			  struct usb_request *request);
+u8 cdns3_ep_addr_to_index(u8 ep_addr);
 int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
 int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
 struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
-- 
2.17.1


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

* [RFC PATCH v2 13/15] usb:cdns3: Adds transfer related function.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (11 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-18 10:09 ` [RFC PATCH v2 14/15] usb:cdns3: Adds debugging function Pawel Laszczak
  2018-11-18 10:09 ` [RFC PATCH v2 15/15] usb:cdns3: Feature for changing role Pawel Laszczak
  14 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch implements a set of function handling transfer on none-default
endpoints. For handling transfer controller use cdns3_trb structure.
Each transfer request block (TRB) contains data buffer address,
length and some control bits. Each transfer can consist of many trbs.
Such group of trbs is called transfer descriptors (TD).
Each endpoint has own array of trbs that make up a transfer ring.
The last element on ring is reserved and is set as Link TRB that
point to the first TRB.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/gadget.c | 235 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 233 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 0202ff5f6c90..911d9b8c1c8f 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -239,6 +239,45 @@ void cdns3_gadget_unconfig(struct cdns3_device *priv_dev)
 	priv_dev->out_mem_is_allocated = 0;
 }
 
+/**
+ * cdns3_ep_inc_trb - increment a trb index.
+ * @index: Pointer to the TRB index to increment.
+ * @cs: Cycle state
+ *
+ * The index should never point to the link TRB. After incrementing,
+ * if it is point to the link TRB, wrap around to the beginning and revert
+ * cycle state bit The
+ * link TRB is always at the last TRB entry.
+ */
+static void cdns3_ep_inc_trb(int *index, u8 *cs)
+{
+	(*index)++;
+	if (*index == (TRBS_PER_SEGMENT - 1)) {
+		*index = 0;
+		*cs ^=  1;
+	}
+}
+
+/**
+ * cdns3_ep_inc_enq - increment endpoint's enqueue pointer
+ * @priv_ep: The endpoint whose enqueue pointer we're incrementing
+ */
+static void cdns3_ep_inc_enq(struct cdns3_endpoint *priv_ep)
+{
+	priv_ep->free_trbs--;
+	cdns3_ep_inc_trb(&priv_ep->enqueue, &priv_ep->pcs);
+}
+
+/**
+ * cdns3_ep_inc_deq - increment endpoint's dequeue pointer
+ * @priv_ep: The endpoint whose dequeue pointer we're incrementing
+ */
+static void cdns3_ep_inc_deq(struct cdns3_endpoint *priv_ep)
+{
+	priv_ep->free_trbs++;
+	cdns3_ep_inc_trb(&priv_ep->dequeue, &priv_ep->ccs);
+}
+
 void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
 {
 	if (enable)
@@ -278,7 +317,27 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
 			   struct cdns3_request *priv_req,
 			   int status)
 {
-	//TODO: Implements this function.
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct usb_request *request = &priv_req->request;
+
+	list_del_init(&request->list);
+	if (request->status == -EINPROGRESS)
+		request->status = status;
+
+	usb_gadget_unmap_request_by_dev(priv_dev->sysdev, request,
+					priv_ep->dir);
+
+	priv_req->on_ring = 0;
+
+	if (request->complete) {
+		spin_unlock(&priv_dev->lock);
+		usb_gadget_giveback_request(&priv_ep->endpoint,
+					    request);
+		spin_lock(&priv_dev->lock);
+	}
+
+	if (request->buf == priv_dev->zlp_buf)
+		cdns3_gadget_ep_free_request(&priv_ep->endpoint, request);
 }
 
 /**
@@ -290,13 +349,185 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
 int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
 			  struct usb_request *request)
 {
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct cdns3_request *priv_req;
+	struct cdns3_trb *trb;
+	dma_addr_t trb_dma;
+	int sg_iter = 0;
+	u32 first_pcs;
+	int  num_trb;
+	int address;
+	int pcs;
+
+	if (!request)
+		return -EINVAL;
+
+	num_trb = request->num_sgs ? request->num_sgs : 1;
+
+	if (num_trb > priv_ep->free_trbs)
+		return -EINVAL;
+
+	priv_req = to_cdns3_request(request);
+	address = priv_ep->endpoint.desc->bEndpointAddress;
+
+	if (priv_req->on_ring)
+		goto arm;
+
+	priv_ep->flags |= EP_PENDING_REQUEST;
+	trb_dma = request->dma;
+
+	/* must allocate buffer aligned to 8 */
+	if ((request->dma % ADDR_MODULO_8)) {
+		if (request->length <= CDNS3_UNALIGNED_BUF_SIZE) {
+			memcpy(priv_ep->aligned_buff, request->buf,
+			       request->length);
+			trb_dma = priv_ep->aligned_dma_addr;
+		} else {
+			return -ENOMEM;
+		}
+	}
+
+	trb = priv_ep->trb_pool + priv_ep->enqueue;
+	priv_req->trb = trb;
+	priv_req->start_trb = priv_ep->enqueue;
+
+	//prepare ring
+	if ((priv_ep->enqueue + num_trb)  >= (TRBS_PER_SEGMENT - 1)) {
+		/*updating C bt in  Link TRB before starting DMA*/
+		struct cdns3_trb *link_trb = priv_ep->trb_pool +
+					     (TRBS_PER_SEGMENT - 1);
+		link_trb->control = ((priv_ep->pcs) ? TRB_CYCLE : 0) |
+				    TRB_TYPE(TRB_LINK) | TRB_CHAIN |
+				    TRB_TOGGLE;
+	}
+
+	first_pcs = priv_ep->pcs ? TRB_CYCLE : 0;
+
+	do {
+	/* fill TRB */
+		trb->buffer = TRB_BUFFER(request->num_sgs == 0
+				? trb_dma : request->sg[sg_iter].dma_address);
+
+		trb->length = TRB_BURST_LEN(16) |
+		    TRB_LEN(request->num_sgs == 0 ?
+				request->length : request->sg[sg_iter].length);
+
+		trb->control = TRB_TYPE(TRB_NORMAL);
+		pcs = priv_ep->pcs ? TRB_CYCLE : 0;
+
+		/*
+		 * first trb should be prepared as last to avoid processing
+		 *  transfer to early
+		 */
+		if (sg_iter == request->num_sgs && sg_iter != 0)
+			trb->control |= pcs | TRB_IOC | TRB_ISP;
+		else if (sg_iter != 0)
+			trb->control |= pcs;
+
+		++sg_iter;
+		++trb;
+		cdns3_ep_inc_enq(priv_ep);
+	} while (sg_iter < request->num_sgs);
+
+	trb = priv_req->trb;
+
+	/* give the TD to the consumer*/
+	if (sg_iter == 1)
+		trb->control |= first_pcs | TRB_IOC | TRB_ISP;
+	else
+		trb->control |= first_pcs;
+
+	priv_req->on_ring = 1;
+arm:
+	/* arm transfer on selected endpoint */
+	cdns3_select_ep(priv_ep->cdns3_dev, address);
+
+	/*
+	 * For DMULT mode we can set address to transfer ring only once after
+	 * enabling endpoint.
+	 */
+	if (priv_ep->flags & EP_UPDATE_EP_TRBADDR) {
+		writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma),
+		       &priv_dev->regs->ep_traddr);
+		priv_ep->flags &= ~EP_UPDATE_EP_TRBADDR;
+	}
+
+	if (priv_dev->hw_configured_flag) {
+		/*clearing TRBERR before seting DRDY*/
+		writel(EP_STS_TRBERR, &priv_dev->regs->ep_sts);
+		/* memory barrier*/
+		wmb();
+		dev_dbg(&priv_dev->dev, "//Ding Dong %s ep_trbaddr %08x\n",
+			priv_ep->name, readl(&priv_dev->regs->ep_traddr));
+		writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd);
+	}
+
 	return 0;
 }
 
+static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep,
+				  struct cdns3_request *ss_request)
+{
+	int current_index;
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+	struct cdns3_trb *trb = ss_request->trb;
+
+	cdns3_select_ep(priv_dev, priv_ep->endpoint.desc->bEndpointAddress);
+	current_index = (readl(&priv_dev->regs->ep_traddr) -
+			 priv_ep->trb_pool_dma) / TRB_SIZE;
+
+	trb = &priv_ep->trb_pool[ss_request->start_trb];
+
+	if ((trb->control  & TRB_CYCLE) != priv_ep->ccs)
+		return false;
+
+	/**
+	 * case where ep_traddr point to last trb in ring (link trb)
+	 * and dequeue pointer already has been changed to first trb
+	 */
+	if ((current_index == (TRBS_PER_SEGMENT - 1)) && !priv_ep->dequeue)
+		return false;
+
+	if (ss_request->start_trb != current_index)
+		return true;
+
+	return false;
+}
+
 static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
 				     struct cdns3_endpoint *priv_ep)
 {
-	//TODO: Implements this function.
+	struct usb_request *request, *request_temp;
+	struct cdns3_request *priv_req;
+	struct cdns3_trb *trb;
+
+	list_for_each_entry_safe(request, request_temp,
+				 &priv_ep->request_list, list) {
+		priv_req = to_cdns3_request(request);
+
+		if (!cdns3_request_handled(priv_ep, priv_req))
+			return;
+
+		if (request->dma % ADDR_MODULO_8 &&
+		    priv_ep->dir == USB_DIR_OUT)
+			memcpy(request->buf, priv_ep->aligned_buff,
+			       request->length);
+
+		trb = priv_ep->trb_pool + priv_ep->dequeue;
+
+		if (trb != priv_req->trb)
+			dev_warn(&priv_dev->dev, "request_trb=0x%p, queue_trb=0x%p\n",
+				 priv_req->trb, trb);
+
+		request->actual = TRB_LEN(le32_to_cpu(trb->length));
+
+		priv_ep->free_trbs++;
+		cdns3_ep_inc_deq(priv_ep);
+
+		cdns3_gadget_giveback(priv_ep, priv_req, 0);
+	}
+
+	priv_ep->flags &= ~EP_PENDING_REQUEST;
 }
 
 /**
-- 
2.17.1


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

* [RFC PATCH v2 14/15] usb:cdns3: Adds debugging function.
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (12 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 13/15] usb:cdns3: Adds transfer " Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  2018-11-18 10:09 ` [RFC PATCH v2 15/15] usb:cdns3: Feature for changing role Pawel Laszczak
  14 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch implements some function used for debugging driver.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/Makefile |   4 +-
 drivers/usb/cdns3/debug.c  | 128 +++++++++++++++++++++++++++++++++++++
 drivers/usb/cdns3/ep0.c    |   3 +
 drivers/usb/cdns3/gadget.c |  12 ++++
 drivers/usb/cdns3/gadget.h |  13 +++-
 5 files changed, 157 insertions(+), 3 deletions(-)
 create mode 100644 drivers/usb/cdns3/debug.c

diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index bea6173bf37f..34e60d03c4ec 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -2,6 +2,6 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
 obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
 
 cdns3-y					:= core.o drd.o
-cdns3-$(CONFIG_USB_CDNS3_GADGET)	+= gadget.o ep0.o
-cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
+cdns3-$(CONFIG_USB_CDNS3_GADGET)	+= gadget.o ep0.o debug.o
+cdns3-$(CONFIG_USB_CDNS3_HOST)		+= host.o
 cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/debug.c b/drivers/usb/cdns3/debug.c
new file mode 100644
index 000000000000..b9a779b35789
--- /dev/null
+++ b/drivers/usb/cdns3/debug.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Driver.
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak <pawell@cadence.com>
+ */
+
+#include "gadget.h"
+
+static char *cdns3_decode_ep_irq(u32 ep_sts, const char *ep_name)
+{
+	static char str[256];
+	int ret;
+
+	ret = sprintf(str, "IRQ for %s: %08x ", ep_name, ep_sts);
+
+	if (ep_sts & EP_STS_SETUP)
+		ret += sprintf(str + ret, "SETUP ");
+	if (ep_sts & EP_STS_IOC)
+		ret += sprintf(str + ret, "IOC ");
+	if (ep_sts & EP_STS_ISP)
+		ret += sprintf(str + ret, "ISP ");
+	if (ep_sts & EP_STS_DESCMIS)
+		ret += sprintf(str + ret, "DESCMIS ");
+	if (ep_sts & EP_STS_STREAMR)
+		ret += sprintf(str + ret, "STREAMR ");
+	if (ep_sts & EP_STS_MD_EXIT)
+		ret += sprintf(str + ret, "MD_EXIT ");
+	if (ep_sts & EP_STS_TRBERR)
+		ret += sprintf(str + ret, "TRBERR ");
+	if (ep_sts & EP_STS_NRDY)
+		ret += sprintf(str + ret, "NRDY ");
+	if (ep_sts & EP_STS_PRIME)
+		ret += sprintf(str + ret, "PRIME ");
+	if (ep_sts & EP_STS_SIDERR)
+		ret += sprintf(str + ret, "SIDERRT ");
+	if (ep_sts & EP_STS_OUTSMM)
+		ret += sprintf(str + ret, "OUTSMM ");
+	if (ep_sts & EP_STS_ISOERR)
+		ret += sprintf(str + ret, "ISOERR ");
+	if (ep_sts & EP_STS_IOT)
+		ret += sprintf(str + ret, "IOT ");
+
+	return str;
+}
+
+char *cdns3_decode_epx_irq(struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
+
+	return cdns3_decode_ep_irq(readl(&priv_dev->regs->ep_sts),
+				   priv_ep->name);
+}
+
+char *cdns3_decode_ep0_irq(struct cdns3_device *priv_dev, int dir)
+{
+	if (dir)
+		return cdns3_decode_ep_irq(readl(&priv_dev->regs->ep_sts),
+					   "ep0IN");
+	else
+		return cdns3_decode_ep_irq(readl(&priv_dev->regs->ep_sts),
+					   "ep0OUT");
+}
+
+void cdns3_dbg_setup(struct cdns3_device *priv_dev)
+{
+	struct usb_ctrlrequest *setup = priv_dev->setup;
+
+	dev_dbg(&priv_dev->dev,
+		"SETUP BRT: %02x BR: %02x V: %04x I: %04x L: %04x\n",
+		setup->bRequestType,
+		setup->bRequest,
+		le16_to_cpu(setup->wValue),
+		le16_to_cpu(setup->wIndex),
+		le16_to_cpu(setup->wLength));
+}
+
+/**
+ * Debug a transfer ring.
+ *
+ * Prints out all TRBs in the endpoint ring, even those after the Link TRB.
+ *.
+ */
+void cdns3_dbg_ring(struct cdns3_device *priv_dev,
+		    struct cdns3_endpoint *priv_ep)
+{
+	u64 addr = priv_ep->trb_pool_dma;
+	struct cdns3_trb *trb;
+	int i;
+
+	for (i = 0; i < TRBS_PER_SEGMENT; ++i) {
+		trb = &priv_ep->trb_pool[i];
+		dev_dbg(&priv_dev->dev, "@%016llx %08x %08x %08x\n", addr,
+			le32_to_cpu(trb->buffer),
+			le32_to_cpu(trb->length),
+			le32_to_cpu(trb->control));
+		addr += sizeof(*trb);
+	}
+}
+
+void cdns3_dbg_ring_ptrs(struct cdns3_device *priv_dev,
+			 struct cdns3_endpoint *priv_ep)
+{
+	struct cdns3_trb *trb;
+
+	trb = &priv_ep->trb_pool[priv_ep->dequeue];
+	dev_dbg(&priv_dev->dev,
+		"Ring deq index: %d, trb: %p (virt), 0x%llx (dma)\n",
+		priv_ep->dequeue, trb,
+		(unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb));
+
+	trb = &priv_ep->trb_pool[priv_ep->enqueue];
+	dev_dbg(&priv_dev->dev,
+		"Ring enq index: %d, trb: %p (virt), 0x%llx (dma)\n",
+		priv_ep->enqueue, trb,
+		(unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb));
+}
+
+void cdns3_dbg_ep_rings(struct cdns3_device *priv_dev,
+			struct cdns3_endpoint *priv_ep)
+{
+	dev_dbg(&priv_dev->dev, "Endpoint ring %s:\n", priv_ep->name);
+
+	cdns3_dbg_ring_ptrs(priv_dev, priv_ep);
+	cdns3_dbg_ring(priv_dev, priv_ep);
+}
diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
index 6f33d98f7684..8807cb02d7b7 100644
--- a/drivers/usb/cdns3/ep0.c
+++ b/drivers/usb/cdns3/ep0.c
@@ -605,12 +605,15 @@ void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir)
 	cdns3_select_ep(priv_dev, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT));
 	ep_sts_reg = readl(&regs->ep_sts);
 
+	dev_dbg(&priv_dev->dev, "%s\n", cdns3_decode_ep0_irq(priv_dev, dir));
+
 	__pending_setup_status_handler(priv_dev);
 
 	if ((ep_sts_reg & EP_STS_SETUP) && dir == 0) {
 		struct usb_ctrlrequest *setup = priv_dev->setup;
 
 		writel(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
+		cdns3_dbg_setup(priv_dev);
 
 		priv_dev->ep0_data_dir = setup->bRequestType & USB_DIR_IN;
 		cdns3_ep0_setup_phase(priv_dev);
diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
index 911d9b8c1c8f..97bdb292eaf2 100644
--- a/drivers/usb/cdns3/gadget.c
+++ b/drivers/usb/cdns3/gadget.c
@@ -122,6 +122,14 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
 	writel(ep, &priv_dev->regs->ep_sel);
 }
 
+dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep,
+				 struct cdns3_trb *trb)
+{
+	u32 offset = (char *)trb - (char *)priv_ep->trb_pool;
+
+	return priv_ep->trb_pool_dma + offset;
+}
+
 /**
  * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint
  * @priv_ep:  endpoint object
@@ -438,6 +446,8 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
 		trb->control |= first_pcs;
 
 	priv_req->on_ring = 1;
+
+	cdns3_dbg_ep_rings(priv_dev, priv_ep);
 arm:
 	/* arm transfer on selected endpoint */
 	cdns3_select_ep(priv_ep->cdns3_dev, address);
@@ -547,6 +557,8 @@ static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep)
 	cdns3_select_ep(priv_dev, priv_ep->endpoint.address);
 	ep_sts_reg = readl(&regs->ep_sts);
 
+	dev_dbg(&priv_dev->dev, "%s\n", cdns3_decode_epx_irq(priv_ep));
+
 	if (ep_sts_reg & EP_STS_TRBERR)
 		writel(EP_STS_TRBERR, &regs->ep_sts);
 
diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
index db8c6cb9f2a5..db5ab6b6c969 100644
--- a/drivers/usb/cdns3/gadget.h
+++ b/drivers/usb/cdns3/gadget.h
@@ -1089,5 +1089,16 @@ struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
 void cdns3_gadget_ep_free_request(struct usb_ep *ep,
 				  struct usb_request *request);
 int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request);
-
+dma_addr_t cdns3_trb_virt_to_dma(struct cdns3_endpoint *priv_ep,
+				 struct cdns3_trb *trb);
+
+char *cdns3_decode_epx_irq(struct cdns3_endpoint *priv_ep);
+char *cdns3_decode_ep0_irq(struct cdns3_device *priv_dev, int dir);
+void cdns3_dbg_ring(struct cdns3_device *priv_dev,
+		    struct cdns3_endpoint *priv_ep);
+void cdns3_dbg_ring_ptrs(struct cdns3_device *priv_dev,
+			 struct cdns3_endpoint *priv_ep);
+void cdns3_dbg_ep_rings(struct cdns3_device *priv_dev,
+			struct cdns3_endpoint *priv_ep);
+void cdns3_dbg_setup(struct cdns3_device *priv_dev);
 #endif /* __LINUX_CDNS3_GADGET */
-- 
2.17.1


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

* [RFC PATCH v2 15/15] usb:cdns3: Feature for changing role
  2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
                   ` (13 preceding siblings ...)
  2018-11-18 10:09 ` [RFC PATCH v2 14/15] usb:cdns3: Adds debugging function Pawel Laszczak
@ 2018-11-18 10:09 ` Pawel Laszczak
  14 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-18 10:09 UTC (permalink / raw)
  To: devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, peter.chen, pjez, kurahul, Pawel Laszczak

Patch adds feature that allow to change role from user space.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/cdns3/Makefile  |  2 +-
 drivers/usb/cdns3/core.c    |  2 +
 drivers/usb/cdns3/debugfs.c | 93 +++++++++++++++++++++++++++++++++++++
 drivers/usb/cdns3/drd.h     |  3 ++
 4 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 drivers/usb/cdns3/debugfs.c

diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
index 34e60d03c4ec..08e6cdbebd46 100644
--- a/drivers/usb/cdns3/Makefile
+++ b/drivers/usb/cdns3/Makefile
@@ -1,7 +1,7 @@
 obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
 obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
 
-cdns3-y					:= core.o drd.o
+cdns3-y					:= core.o drd.o debugfs.o
 cdns3-$(CONFIG_USB_CDNS3_GADGET)	+= gadget.o ep0.o debug.o
 cdns3-$(CONFIG_USB_CDNS3_HOST)		+= host.o
 cdns3-pci-y		 		:= cdns3-pci-wrap.o
diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index 1fa233415901..304323b643f2 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -323,6 +323,7 @@ static int cdns3_probe(struct platform_device *pdev)
 		goto err2;
 	}
 
+	cdns3_debugfs_init(cdns);
 	device_set_wakeup_capable(dev, true);
 	pm_runtime_set_active(dev);
 	pm_runtime_enable(dev);
@@ -358,6 +359,7 @@ static int cdns3_remove(struct platform_device *pdev)
 	pm_runtime_get_sync(&pdev->dev);
 	pm_runtime_disable(&pdev->dev);
 	pm_runtime_put_noidle(&pdev->dev);
+	cdns3_debugfs_exit(cdns);
 	cdns3_remove_roles(cdns);
 
 	return 0;
diff --git a/drivers/usb/cdns3/debugfs.c b/drivers/usb/cdns3/debugfs.c
new file mode 100644
index 000000000000..189b12d96cbf
--- /dev/null
+++ b/drivers/usb/cdns3/debugfs.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence USBSS DRD Controller DebugFS filer.
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak <pawell@cadence.com>
+ */
+
+#include <linux/types.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+#include "core.h"
+#include "gadget.h"
+
+static int cdns3_mode_show(struct seq_file *s, void *unused)
+{
+	struct cdns3		*cdns = s->private;
+
+	switch (cdns->current_dr_mode) {
+	case USB_DR_MODE_HOST:
+		seq_puts(s, "host\n");
+		break;
+	case USB_DR_MODE_PERIPHERAL:
+		seq_puts(s, "device\n");
+		break;
+	case USB_DR_MODE_OTG:
+		seq_puts(s, "otg\n");
+		break;
+	default:
+		seq_puts(s, "UNKNOWN mode\n");
+	}
+
+	return 0;
+}
+
+static int cdns3_mode_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, cdns3_mode_show, inode->i_private);
+}
+
+static ssize_t cdns3_mode_write(struct file *file,
+				const char __user *ubuf,
+				size_t count, loff_t *ppos)
+{
+	struct seq_file		*s = file->private_data;
+	struct cdns3		*cdns = s->private;
+	u32			mode = 0;
+	char			buf[32];
+
+	if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+		return -EFAULT;
+
+	if (!strncmp(buf, "host", 4))
+		mode = USB_DR_MODE_HOST;
+
+	if (!strncmp(buf, "device", 6))
+		mode = USB_DR_MODE_PERIPHERAL;
+
+	if (!strncmp(buf, "otg", 3))
+		mode = USB_DR_MODE_OTG;
+
+	cdns->desired_dr_mode = mode;
+	queue_work(system_freezable_wq, &cdns->role_switch_wq);
+	return count;
+}
+
+static const struct file_operations cdns3_mode_fops = {
+	.open			= cdns3_mode_open,
+	.write			= cdns3_mode_write,
+	.read			= seq_read,
+	.llseek			= seq_lseek,
+	.release		= single_release,
+};
+
+void cdns3_debugfs_init(struct cdns3 *cdns)
+{
+	struct dentry *root;
+
+	root = debugfs_create_dir(dev_name(cdns->dev), NULL);
+	cdns->root = root;
+	if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET) &&
+	    IS_ENABLED(CONFIG_USB_CDNS3_HOST))
+		debugfs_create_file("mode", 0644, root, cdns,
+				    &cdns3_mode_fops);
+}
+
+void cdns3_debugfs_exit(struct cdns3 *cdns)
+{
+	debugfs_remove_recursive(cdns->root);
+}
diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
index 0faa7520ecac..8367032e12a3 100644
--- a/drivers/usb/cdns3/drd.h
+++ b/drivers/usb/cdns3/drd.h
@@ -119,4 +119,7 @@ int cdns3_drd_init(struct cdns3 *cdns);
 int cdns3_drd_update_mode(struct cdns3 *cdns);
 irqreturn_t cdns3_drd_irq(struct cdns3 *cdns);
 
+void cdns3_debugfs_init(struct cdns3 *cdns);
+void cdns3_debugfs_exit(struct cdns3 *cdns);
+
 #endif /* __LINUX_CDNS3_DRD */
-- 
2.17.1


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

* Re: [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper.
  2018-11-18 10:08 ` [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper Pawel Laszczak
@ 2018-11-23 10:44   ` Roger Quadros
  0 siblings, 0 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-23 10:44 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

Hi Pawel,

On 18/11/18 12:08, Pawel Laszczak wrote:
> Patch adds PCI specific glue driver that creates and registers in-system
> cdns-usb3 platform device. Thanks to that we will be able to use
> the cdns-usb3 platform driver for USBSS-DEV controller
> build on PCI board
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/Kconfig                |   2 +
>  drivers/usb/Makefile               |   2 +
>  drivers/usb/cdns3/Kconfig          |  24 +++++
>  drivers/usb/cdns3/Makefile         |   3 +
>  drivers/usb/cdns3/cdns3-pci-wrap.c | 157 +++++++++++++++++++++++++++++
>  5 files changed, 188 insertions(+)
>  create mode 100644 drivers/usb/cdns3/Kconfig
>  create mode 100644 drivers/usb/cdns3/Makefile
>  create mode 100644 drivers/usb/cdns3/cdns3-pci-wrap.c
> 
> diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
> index 987fc5ba6321..5f9334019d04 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -112,6 +112,8 @@ source "drivers/usb/usbip/Kconfig"
>  
>  endif
>  
> +source "drivers/usb/cdns3/Kconfig"
> +
>  source "drivers/usb/mtu3/Kconfig"
>  
>  source "drivers/usb/musb/Kconfig"
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index 7d1b8c82b208..82093a60ea2c 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -8,6 +8,8 @@
>  obj-$(CONFIG_USB)		+= core/
>  obj-$(CONFIG_USB_SUPPORT)	+= phy/
>  
> +obj-$(CONFIG_USB_CDNS3)		+= cdns3/
> +
>  obj-$(CONFIG_USB_DWC3)		+= dwc3/
>  obj-$(CONFIG_USB_DWC2)		+= dwc2/
>  obj-$(CONFIG_USB_ISP1760)	+= isp1760/
> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
> new file mode 100644
> index 000000000000..eb22a8692991
> --- /dev/null
> +++ b/drivers/usb/cdns3/Kconfig
> @@ -0,0 +1,24 @@
> +config USB_CDNS3
> +	tristate "Cadence USB3 Dual-Role Controller"
> +	depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA
> +	help
> +	  Say Y here if your system has a cadence USB3 dual-role controller.
> +	  It supports: dual-role switch, Host-only, and Peripheral-only.
> +
> +	  If you choose to build this driver is a dynamically linked
> +	  module, the module will be called cdns3.ko.
> +
> +if USB_CDNS3
> +
> +config USB_CDNS3_PCI_WRAP
> +	tristate "PCIe-based Platforms"

"Cadence USB3 support on PCIe-based platforms"?

> +	depends on USB_PCI && ACPI
> +	default USB_CDNS3
> +	help
> +	  If you're using the USBSS Core IP with a PCIe, please say
> +	  'Y' or 'M' here.
> +
> +	  If you choose to build this driver as module it will
> +	  be dynamically linked and module will be called cdns3-pci.ko
> +
> +endif
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> new file mode 100644
> index 000000000000..dcdd62003c6a
> --- /dev/null
> +++ b/drivers/usb/cdns3/Makefile
> @@ -0,0 +1,3 @@
> +obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
> +
> +cdns3-pci-y		 		:= cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c
> new file mode 100644
> index 000000000000..d0a15cc0b738
> --- /dev/null
> +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS PCI Glue driver
> + *
> + * Copyright (C) 2018 Cadence.
> + *
> + * Author: Pawel Laszczak <pawell@cadence.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/platform_device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/slab.h>
> +
> +struct cdns3_wrap {
> +	struct platform_device *plat_dev;
> +	struct pci_dev *hg_dev;
> +	struct resource dev_res[4];
> +};
> +
> +struct cdns3_wrap wrap;
> +
> +#define RES_IRQ_ID		0
> +#define RES_HOST_ID		1
> +#define RES_DEV_ID		2
> +#define RES_DRD_ID		3
> +
> +#define PCI_BAR_HOST		0
> +#define PCI_BAR_DEV		2
> +#define PCI_BAR_OTG		4
> +
> +#define PCI_DEV_FN_HOST_DEVICE	0
> +#define PCI_DEV_FN_OTG		1
> +
> +#define PCI_DRIVER_NAME		"cdns3-pci-usbss"
> +#define PLAT_DRIVER_NAME	"cdns-usb3"
> +
> +#define CDNS_VENDOR_ID 0x17cd
> +#define CDNS_DEVICE_ID 0x0100
> +
> +/**
> + * cdns3_pci_probe - Probe function for Cadence USB wrapper driver
> + * @pdev: platform device object
> + * @id: pci device id
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_pci_probe(struct pci_dev *pdev,
> +			   const struct pci_device_id *id)
> +{
> +	struct platform_device_info plat_info;
> +	struct cdns3_wrap *wrap;
> +	struct resource *res;
> +	int err;
> +
> +	/*
> +	 * for GADGET/HOST PCI (devfn) function number is 0,
> +	 * for OTG PCI (devfn) function number is 1
> +	 */
> +	if (!id || pdev->devfn != PCI_DEV_FN_HOST_DEVICE)
> +		return -EINVAL;
> +
> +	err = pcim_enable_device(pdev);
> +	if (err) {
> +		dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", err);
> +		return err;
> +	}
> +
> +	pci_set_master(pdev);
> +	wrap = devm_kzalloc(&pdev->dev, sizeof(*wrap), GFP_KERNEL);
> +	if (!wrap) {
> +		dev_err(&pdev->dev, "Failed to load PCI module\n");

"Failed to allocate memory" ?

Rest of patch looks good to me.

> +		return -ENOMEM;
> +	}
> +
> +	/* function 0: host(BAR_0) + device(BAR_1) + otg(BAR_2)). */
> +	memset(wrap->dev_res, 0x00,
> +	       sizeof(struct resource) * ARRAY_SIZE(wrap->dev_res));
> +	dev_dbg(&pdev->dev, "Initialize Device resources\n");
> +	res = wrap->dev_res;
> +
> +	res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV);
> +	res[RES_DEV_ID].end =   pci_resource_end(pdev, PCI_BAR_DEV);
> +	res[RES_DEV_ID].name = "cdns3-dev-regs";
> +	res[RES_DEV_ID].flags = IORESOURCE_MEM;
> +	dev_dbg(&pdev->dev, "USBSS-DEV physical base addr: %pa\n",
> +		&res[RES_DEV_ID].start);
> +
> +	res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST);
> +	res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST);
> +	res[RES_HOST_ID].name = "cdns3-xhci-regs";
> +	res[RES_HOST_ID].flags = IORESOURCE_MEM;
> +	dev_dbg(&pdev->dev, "USBSS-XHCI physical base addr: %pa\n",
> +		&res[RES_HOST_ID].start);
> +
> +	res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG);
> +	res[RES_DRD_ID].end =   pci_resource_end(pdev, PCI_BAR_OTG);
> +	res[RES_DRD_ID].name = "cdns3-otg";
> +	res[RES_DRD_ID].flags = IORESOURCE_MEM;
> +	dev_dbg(&pdev->dev, "USBSS-DRD physical base addr: %pa\n",
> +		&res[RES_DRD_ID].start);
> +
> +	/* Interrupt common for both device and XHCI */
> +	wrap->dev_res[RES_IRQ_ID].start = pdev->irq;
> +	wrap->dev_res[RES_IRQ_ID].name = "cdns3-irq";
> +	wrap->dev_res[RES_IRQ_ID].flags = IORESOURCE_IRQ;
> +
> +	/* set up platform device info */
> +	memset(&plat_info, 0, sizeof(plat_info));
> +	plat_info.parent = &pdev->dev;
> +	plat_info.fwnode = pdev->dev.fwnode;
> +	plat_info.name = PLAT_DRIVER_NAME;
> +	plat_info.id = pdev->devfn;
> +	plat_info.res = wrap->dev_res;
> +	plat_info.num_res = ARRAY_SIZE(wrap->dev_res);
> +	plat_info.dma_mask = pdev->dma_mask;
> +
> +	/* register platform device */
> +	wrap->plat_dev = platform_device_register_full(&plat_info);
> +	if (IS_ERR(wrap->plat_dev)) {
> +		err = PTR_ERR(wrap->plat_dev);
> +		return err;
> +	}
> +
> +	pci_set_drvdata(pdev, wrap);
> +
> +	return err;
> +}
> +
> +void cdns3_pci_remove(struct pci_dev *pdev)
> +{
> +	struct cdns3_wrap *wrap = (struct cdns3_wrap *)pci_get_drvdata(pdev);
> +
> +	platform_device_unregister(wrap->plat_dev);
> +}
> +
> +static const struct pci_device_id cdns3_pci_ids[] = {
> +	{ PCI_DEVICE(CDNS_VENDOR_ID, CDNS_DEVICE_ID), },
> +	{ 0, }
> +};
> +
> +static struct pci_driver cdns3_pci_driver = {
> +	.name = PCI_DRIVER_NAME,
> +	.id_table = cdns3_pci_ids,
> +	.probe = cdns3_pci_probe,
> +	.remove = cdns3_pci_remove,
> +};
> +
> +module_pci_driver(cdns3_pci_driver);
> +MODULE_DEVICE_TABLE(pci, cdns3_pci_ids);
> +
> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Cadence USBSS PCI wrapperr");
> +
> 

cheers,
-roger
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller.
  2018-11-18 10:08 ` [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller Pawel Laszczak
@ 2018-11-23 10:53   ` Roger Quadros
  2018-11-25  7:33     ` Pawel Laszczak
  2018-12-04 22:41   ` Rob Herring
  1 sibling, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-23 10:53 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

On 18/11/18 12:08, Pawel Laszczak wrote:
> Thsi patch aim at documenting USB related dt-bindings for the

s/Thsi/This

> Cadence USBSS-DRD controller.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  .../devicetree/bindings/usb/cdns3-usb.txt       | 17 +++++++++++++++++
>  1 file changed, 17 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/usb/cdns3-usb.txt
> 
> diff --git a/Documentation/devicetree/bindings/usb/cdns3-usb.txt b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
> new file mode 100644
> index 000000000000..f9e953f32d47
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
> @@ -0,0 +1,17 @@
> +Binding for the Cadence USBSS-DRD controller
> +
> +Required properties:
> +  - reg: Physical base address and size of the controller's register area.
> +  - compatible: Should contain: "cdns,usb3"
> +  - interrupts: Interrupt specifier. Refer to interrupt bindings.

Do you also comply with USB generic bindings for dr_mode, speed, etc?
i.e. Documentation/devicetree/bindings/usb/generic.txt

If yes it must be mentioned in Optional properties:

> +
> +
> +Example:
> +	cdns3@f3000000 {
> +		compatible = "cdns,usb3";
> +		interrupts = <USB_IRQ  7 IRQ_TYPE_LEVEL_HIGH>;
> +		reg = <0xf3000000 0x10000	//memory area for OTG/DRD registers
> +			0xf3010000 0x10000	//memory area for HOST registers
> +			0xf3020000 0x10000>;	//memory area for Device registers
> +	};
> +
> 

cheers,
-roger
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-18 10:09 ` [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code Pawel Laszczak
@ 2018-11-23 11:35   ` Roger Quadros
  2018-11-25 12:35     ` Pawel Laszczak
  2018-12-04  8:50     ` Peter Chen
  2018-11-30  7:32   ` Peter Chen
  1 sibling, 2 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-23 11:35 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch adds core.c and core.h file that implements initialization
> of platform driver and adds function responsible for selecting,
> switching and running appropriate Device/Host mode.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/Makefile |   2 +
>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
>  drivers/usb/cdns3/core.h   | 100 +++++++++
>  3 files changed, 515 insertions(+)
>  create mode 100644 drivers/usb/cdns3/core.c
>  create mode 100644 drivers/usb/cdns3/core.h
> 
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> index dcdd62003c6a..02d25b23c5d3 100644
> --- a/drivers/usb/cdns3/Makefile
> +++ b/drivers/usb/cdns3/Makefile
> @@ -1,3 +1,5 @@
> +obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>  
> +cdns3-y					:= core.o
>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> new file mode 100644
> index 000000000000..f9055d4da67f
> --- /dev/null
> +++ b/drivers/usb/cdns3/core.c
> @@ -0,0 +1,413 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver.
> + *
> + * Copyright (C) 2018 Cadence.
> + *
> + * Author: Peter Chen <peter.chen@nxp.com>
> + *         Pawel Laszczak <pawell@cadence.com>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "gadget.h"
> +#include "core.h"
> +
> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> +{
> +	WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
> +	return cdns->roles[cdns->role];
> +}
> +
> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
> +{
> +	int ret;
> +
> +	if (role >= CDNS3_ROLE_END)

WARN_ON()?

> +		return 0;
> +
> +	if (!cdns->roles[role])
> +		return -ENXIO;
> +
> +	mutex_lock(&cdns->mutex);
> +	cdns->role = role;
> +	ret = cdns->roles[role]->start(cdns);
> +	mutex_unlock(&cdns->mutex);
> +	return ret;
> +}
> +
> +static inline void cdns3_role_stop(struct cdns3 *cdns)
> +{
> +	enum cdns3_roles role = cdns->role;
> +
> +	if (role == CDNS3_ROLE_END)

WARN_ON(role >= CNDS3_ROLE_END) ?

> +		return;
> +
> +	mutex_lock(&cdns->mutex);
> +	cdns->roles[role]->stop(cdns);
> +	cdns->role = CDNS3_ROLE_END;

Why change the role here? You are just stopping the role not changing it.
I think cdns->role should remain unchanged, so we can call cdns3_role_start()
if required without error.

> +	mutex_unlock(&cdns->mutex);
> +}
> +
> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
> +{
> +	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> +		//TODO: implements selecting device/host mode
> +		return CDNS3_ROLE_HOST;
> +	}
> +	return cdns->roles[CDNS3_ROLE_HOST]
> +		? CDNS3_ROLE_HOST
> +		: CDNS3_ROLE_GADGET;

Why not just
	return cdns->role;

I'm wondering if we really need this function.
> +}

> +
> +/**
> + * cdns3_core_init_role - initialize role of operation
> + * @cdns: Pointer to cdns3 structure
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_core_init_role(struct cdns3 *cdns)
> +{
> +	struct device *dev = cdns->dev;
> +	enum usb_dr_mode dr_mode;
> +
> +	dr_mode = usb_get_dr_mode(dev);
> +	cdns->role = CDNS3_ROLE_END;
> +
> +	/*
> +	 * If driver can't read mode by means of usb_get_dr_mdoe function then
> +	 * chooses mode according with Kernel configuration. This setting
> +	 * can be restricted later depending on strap pin configuration.
> +	 */
> +	if (dr_mode == USB_DR_MODE_UNKNOWN) {
> +		if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
> +		    IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> +			dr_mode = USB_DR_MODE_OTG;
> +		else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
> +			dr_mode = USB_DR_MODE_HOST;
> +		else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> +			dr_mode = USB_DR_MODE_PERIPHERAL;
> +	}
> +
> +	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
> +		//TODO: implements host initialization

		/* TODO: Add host role */ ?

> +	}
> +
> +	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
> +		//TODO: implements device initialization

		/* TODO: Add device role */ ?

> +	}
> +
> +	if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
> +		dev_err(dev, "no supported roles\n");
> +		return -ENODEV;
> +	}
> +
> +	cdns->dr_mode = dr_mode;
> +	return 0;
> +}
> +
> +/**
> + * cdns3_irq - interrupt handler for cdns3 core device
> + *
> + * @irq: irq number for cdns3 core device
> + * @data: structure of cdns3
> + *
> + * Returns IRQ_HANDLED or IRQ_NONE
> + */
> +static irqreturn_t cdns3_irq(int irq, void *data)
> +{
> +	struct cdns3 *cdns = data;
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	/* Handle device/host interrupt */
> +	if (cdns->role != CDNS3_ROLE_END)

Is it because of this that you need to set role to END at role_stop?
I think it is better to add a state variable to struct cdns3_role_driver, so we can
check if it is active or stopped.

e.g.
	if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)

> +		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
> +
> +	return ret;
> +}
> +
> +static void cdns3_remove_roles(struct cdns3 *cdns)

Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?

> +{
> +	//TODO: implements this function
> +}

> +
> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
> +{
> +	enum cdns3_roles current_role;
> +	int ret = 0;
> +
> +	current_role = cdns->role;
> +
> +	if (role == CDNS3_ROLE_END)
> +		return 0;

role == END looks like error state. and it should never happen.
WARN here?

> +
> +	dev_dbg(cdns->dev, "Switching role");
> +

Don't you have to stop the previous role before starting the new role?

> +	ret = cdns3_role_start(cdns, role);
> +	if (ret) {
> +		/* Back to current role */
> +		dev_err(cdns->dev, "set %d has failed, back to %d\n",
> +			role, current_role);
> +		ret = cdns3_role_start(cdns, current_role);
> +	}
> +
> +	return ret;
> +}
> +
> +/**
> + * cdns3_role_switch - work queue handler for role switch
> + *
> + * @work: work queue item structure
> + *
> + * Handles below events:
> + * - Role switch for dual-role devices
> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
> + */
> +static void cdns3_role_switch(struct work_struct *work)
> +{
> +	enum cdns3_roles role = CDNS3_ROLE_END;
> +	struct cdns3 *cdns;
> +	bool device, host;
> +
> +	cdns = container_of(work, struct cdns3, role_switch_wq);
> +
> +	//TODO: implements this functions.
> +	//host = cdns3_is_host(cdns);
> +	//device = cdns3_is_device(cdns);
> +	host = 1;
> +	device = 0;
> +
> +	if (host)
> +		role = CDNS3_ROLE_HOST;
> +	else if (device)
> +		role = CDNS3_ROLE_GADGET;
> +
> +	if (cdns->desired_dr_mode == cdns->current_dr_mode &&
> +	    cdns->role == role)
> +		return;
> +

I think all the below code can be moved to cdns3_do_role_switch().

> +	pm_runtime_get_sync(cdns->dev);
> +	cdns3_role_stop(cdns);
> +
> +	if (host) {
> +		if (cdns->roles[CDNS3_ROLE_HOST])
> +			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
> +		pm_runtime_put_sync(cdns->dev);
> +		return;
> +	}
> +
> +	if (device)
> +		cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
> +	else
> +		cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
> +
> +	pm_runtime_put_sync(cdns->dev);
> +}
> +
> +/**
> + * cdns3_probe - probe for cdns3 core device
> + * @pdev: Pointer to cdns3 core platform device
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct resource	*res;
> +	struct cdns3 *cdns;
> +	void __iomem *regs;
> +	int ret;
> +
> +	cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
> +	if (!cdns)
> +		return -ENOMEM;
> +
> +	cdns->dev = dev;
> +
> +	platform_set_drvdata(pdev, cdns);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> +	if (!res) {
> +		dev_err(dev, "missing IRQ\n");
> +		return -ENODEV;
> +	}
> +	cdns->irq = res->start;
> +
> +	/*
> +	 * Request memory region
> +	 * region-0: xHCI
> +	 * region-1: Peripheral
> +	 * region-2: OTG registers
> +	 */

The memory region order is different from the dt-binding.
There it is OTG, host(xhci), device (peripheral).

> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	regs = devm_ioremap_resource(dev, res);
> +
> +	if (IS_ERR(regs))
> +		return PTR_ERR(regs);
> +	cdns->xhci_regs = regs;
> +	cdns->xhci_res = res;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(regs))
> +		return PTR_ERR(regs);
> +	cdns->dev_regs	= regs;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> +	regs = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(regs))
> +		return PTR_ERR(regs);
> +	cdns->otg_regs = regs;
> +
> +	mutex_init(&cdns->mutex);
> +
> +	cdns->phy = devm_phy_get(dev, "cdns3,usbphy");

"cdns3,usbphy" is not documented in dt-binding.

> +	if (IS_ERR(cdns->phy)) {
> +		dev_info(dev, "no generic phy found\n");
> +		cdns->phy = NULL;
> +		/*
> +		 * fall through here!
> +		 * if no generic phy found, phy init
> +		 * should be done under boot!
> +		 */

No you shouldn't fall through always if it is an error condition.
Something like this should work better.

        if (IS_ERR(cnds->phy)) {
                ret = PTR_ERR(cdns->phy);
                if (ret == -ENOSYS || ret == -ENODEV) {
                        cdns->phy = NULL;
                } else if (ret == -EPROBE_DEFER) {
                        return ret;
                } else {
                        dev_err(dev, "no phy found\n");
                        goto err0;
                }
        }

So if PHY was provided in DT, and PHY support/drivers is present
and error condition means something is wrong and we have to error out.

> +	} else {
> +		phy_init(cdns->phy);
> +	}

You can do phy_init() outside the else.

> +
> +	ret = cdns3_core_init_role(cdns);
> +	if (ret)
> +		goto err1;
> +
> +	INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
> +	if (ret)
> +		goto err2;
> +
> +	if (ret)
> +		goto err2;
> +
> +	cdns->role = cdns3_get_role(cdns);

I think this should move to cdns3_core_init_role().

> +
> +	ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
> +			       dev_name(dev), cdns);
> +
> +	if (ret)
> +		goto err2;

How about moving request_irq to before cdsn3_core_init_role()?

Then you can move cdns3_role_start() as well to core_init_role().

> +
> +	ret = cdns3_role_start(cdns, cdns->role);
> +	if (ret) {
> +		dev_err(dev, "can't start %s role\n",
> +			cdns3_get_current_role_driver(cdns)->name);
> +		goto err2;
> +	}
> +
> +	device_set_wakeup_capable(dev, true);
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +
> +	/*
> +	 * The controller needs less time between bus and controller suspend,
> +	 * and we also needs a small delay to avoid frequently entering low
> +	 * power mode.
> +	 */
> +	pm_runtime_set_autosuspend_delay(dev, 20);
> +	pm_runtime_mark_last_busy(dev);
> +	pm_runtime_use_autosuspend(dev);
> +	dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
> +
> +	return 0;
> +
> +err2:
> +	cdns3_remove_roles(cdns);
> +err1:

phy_exit() ?

> +	return ret;
> +}
> +
> +/**
> + * cdns3_remove - unbind drd driver and clean up
> + * @pdev: Pointer to Linux platform device
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_remove(struct platform_device *pdev)
> +{
> +	struct cdns3 *cdns = platform_get_drvdata(pdev);
> +
> +	pm_runtime_get_sync(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +	pm_runtime_put_noidle(&pdev->dev);
> +	cdns3_remove_roles(cdns);

phy_exit() ?

> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id of_cdns3_match[] = {
> +	{ .compatible = "cdns,usb3" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, of_cdns3_match);
> +#endif
> +
> +#ifdef CONFIG_PM
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int cdns3_suspend(struct device *dev)
> +{
> +	//TODO: implements this function
> +	return 0;
> +}
> +
> +static int cdns3_resume(struct device *dev)
> +{
> +	//TODO: implements this function
> +	return 0;
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +static int cdns3_runtime_suspend(struct device *dev)
> +{	//TODO: implements this function
> +	return 0;
> +}
> +
> +static int cdns3_runtime_resume(struct device *dev)
> +{
> +	//TODO: implements this function
> +	return 0;
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops cdns3_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
> +	SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
> +};
> +
> +static struct platform_driver cdns3_driver = {
> +	.probe		= cdns3_probe,
> +	.remove		= cdns3_remove,
> +	.driver		= {
> +		.name	= "cdns-usb3",
> +		.of_match_table	= of_match_ptr(of_cdns3_match),
> +		.pm	= &cdns3_pm_ops,
> +	},
> +};
> +
> +static int __init cdns3_driver_platform_register(void)
> +{
> +	return platform_driver_register(&cdns3_driver);
> +}
> +module_init(cdns3_driver_platform_register);
> +
> +static void __exit cdns3_driver_platform_unregister(void)
> +{
> +	platform_driver_unregister(&cdns3_driver);
> +}
> +module_exit(cdns3_driver_platform_unregister);
> +
> +MODULE_ALIAS("platform:cdns3");
> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
> new file mode 100644
> index 000000000000..7c8204fe4d3d
> --- /dev/null
> +++ b/drivers/usb/cdns3/core.h
> @@ -0,0 +1,100 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence USBSS DRD Driver.
> + *
> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2018 Cadence.
> + *
> + * Authors: Peter Chen <peter.chen@nxp.com>
> + *          Pawel Laszczak <pawell@cadence.com>
> + */
> +#include <linux/usb/otg.h>
> +
> +#ifndef __LINUX_CDNS3_CORE_H
> +#define __LINUX_CDNS3_CORE_H
> +
> +struct cdns3;
> +enum cdns3_roles {
> +	CDNS3_ROLE_HOST = 0,
> +	CDNS3_ROLE_GADGET,
> +	CDNS3_ROLE_END,
> +};
> +
> +/**
> + * struct cdns3_role_driver - host/gadget role driver
> + * @start: start this role
> + * @stop: stop this role
> + * @suspend: suspend callback for this role
> + * @resume: resume callback for this role
> + * @irq: irq handler for this role
> + * @name: role name string (host/gadget)
> + */
> +struct cdns3_role_driver {
> +	int (*start)(struct cdns3 *cdns);
> +	void (*stop)(struct cdns3 *cdns);
> +	int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
> +	int (*resume)(struct cdns3 *cdns, bool hibernated);
> +	irqreturn_t (*irq)(struct cdns3 *cdns);
> +	const char *name;
> +};
> +
> +#define CDNS3_NUM_OF_CLKS	5
> +/**
> + * struct cdns3 - Representation of Cadence USB3 DRD controller.
> + * @dev: pointer to Cadence device struct
> + * @xhci_regs: pointer to base of xhci registers
> + * @xhci_res: the resource for xhci
> + * @dev_regs: pointer to base of dev registers
> + * @otg_regs: pointer to base of otg registers
> + * @irq: irq number for controller
> + * @roles: array of supported roles for this controller
> + * @role: current role
> + * @host_dev: the child host device pointer for cdns3 core
> + * @gadget_dev: the child gadget device pointer for cdns3 core
> + * @usb: phy for this controller
> + * @role_switch_wq: work queue item for role switch
> + * @in_lpm: the controller in low power mode
> + * @wakeup_int: the wakeup interrupt
> + * @mutex: the mutex for concurrent code at driver
> + * @dr_mode: supported mode of operation it can be only Host, only Device
> + *           or OTG mode that allow to switch between Device and Host mode.
> + *           This field based on hardware configuration and cant't be changed.

But dr_mode can be forced in device-tree. So it isn't really only hardware configuration.

> + * @current_dr_role: current mode of operation when in dual-role mode
> + * @desired_dr_role: desired mode of operation when in dual-role mode.
> + *           This value can be changed during runtime.
> + *           Available options depends on  dr_mode:
> + *           dr_mode                 |  desired_dr_role and current_dr_role
> + *           ----------------------------------------------------------------
> + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
> + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
> + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG

Do you need to update the right hand side to reflect ROLEs instead of MODE?

> + *
> + *           Desired_dr_role can be changed by means of debugfs.
> + * @root: debugfs root folder pointer
> + */
> +struct cdns3 {
> +	struct device			*dev;
> +	void __iomem			*xhci_regs;
> +	struct resource			*xhci_res;
> +	struct cdns3_usb_regs __iomem	*dev_regs;
> +	struct cdns3_otg_regs		*otg_regs;
> +	int irq;
> +	struct cdns3_role_driver	*roles[CDNS3_ROLE_END];
> +	enum cdns3_roles		role;
> +	struct device			*host_dev;
> +	struct device			*gadget_dev;
> +	struct phy			*phy;
> +	struct work_struct		role_switch_wq;
> +	int				in_lpm:1;
> +	int				wakeup_int:1;
> +	/* mutext used in workqueue*/
> +	struct mutex			mutex;
> +	enum usb_dr_mode		dr_mode;
> +	enum usb_dr_mode		current_dr_mode;
> +	enum usb_dr_mode		desired_dr_mode;
> +	struct dentry			*root;
> +};
> +
> +#endif /* __LINUX_CDNS3_CORE_H */
> 

cheers,
-roger
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 06/15] usb:cdns3: Adds Host support
  2018-11-18 10:09 ` [RFC PATCH v2 06/15] usb:cdns3: Adds Host support Pawel Laszczak
@ 2018-11-23 14:23   ` Roger Quadros
  2018-11-26  8:24     ` Pawel Laszczak
  2018-12-05  8:41     ` Peter Chen
  0 siblings, 2 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-23 14:23 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch adds host-export.h and host.c file and mplements functions that
> allow to initialize, start and stop XHCI host driver.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/Kconfig       |  10 ++
>  drivers/usb/cdns3/Makefile      |   1 +
>  drivers/usb/cdns3/core.c        |   7 +-
>  drivers/usb/cdns3/host-export.h |  30 ++++
>  drivers/usb/cdns3/host.c        | 256 ++++++++++++++++++++++++++++++++
>  5 files changed, 302 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/usb/cdns3/host-export.h
>  create mode 100644 drivers/usb/cdns3/host.c
> 
> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
> index eb22a8692991..d92bc3d68eb0 100644
> --- a/drivers/usb/cdns3/Kconfig
> +++ b/drivers/usb/cdns3/Kconfig
> @@ -10,6 +10,16 @@ config USB_CDNS3
>  
>  if USB_CDNS3
>  
> +config USB_CDNS3_HOST
> +        bool "Cadence USB3 host controller"
> +        depends on USB_XHCI_HCD
> +        help
> +          Say Y here to enable host controller functionality of the
> +          cadence driver.
> +
> +          Host controller is compliance with XHCI so it will use
> +          standard XHCI driver.
> +
>  config USB_CDNS3_PCI_WRAP
>  	tristate "PCIe-based Platforms"
>  	depends on USB_PCI && ACPI
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> index e779b2a2f8eb..976117ba67ff 100644
> --- a/drivers/usb/cdns3/Makefile
> +++ b/drivers/usb/cdns3/Makefile
> @@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>  
>  cdns3-y					:= core.o drd.o
> +cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> index dbee4325da7f..4cb820be9ff3 100644
> --- a/drivers/usb/cdns3/core.c
> +++ b/drivers/usb/cdns3/core.c
> @@ -17,6 +17,7 @@
>  
>  #include "gadget.h"
>  #include "core.h"
> +#include "host-export.h"
>  #include "drd.h"
>  
>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> @@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
>  	}
>  
>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
> -		//TODO: implements host initialization
> +		if (cdns3_host_init(cdns))
> +			dev_info(dev, "doesn't support host\n");

dev_err()

And you need to error out with error code.

>  	}
>  
>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
> @@ -142,7 +144,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>  
>  static void cdns3_remove_roles(struct cdns3 *cdns)
>  {
> -	//TODO: implements this function

if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST)

> +	cdns3_host_remove(cdns);

How about calling it cdns3_host_exit() to complement cdns3_host_init().

>  }
>  
>  static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
> @@ -410,6 +412,7 @@ static struct platform_driver cdns3_driver = {
>  
>  static int __init cdns3_driver_platform_register(void)
>  {
> +	cdns3_host_driver_init();
>  	return platform_driver_register(&cdns3_driver);
>  }
>  module_init(cdns3_driver_platform_register);
> diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
> new file mode 100644
> index 000000000000..f8f3b230b472
> --- /dev/null
> +++ b/drivers/usb/cdns3/host-export.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence USBSS DRD Driver -Host Export APIs
> + *
> + * Copyright (C) 2017 NXP
> + *
> + * Authors: Peter Chen <peter.chen@nxp.com>
> + */
> +#ifndef __LINUX_CDNS3_HOST_EXPORT
> +#define __LINUX_CDNS3_HOST_EXPORT
> +
> +#ifdef CONFIG_USB_CDNS3_HOST
> +
> +int cdns3_host_init(struct cdns3 *cdns);
> +void cdns3_host_remove(struct cdns3 *cdns);
> +void cdns3_host_driver_init(void);
> +
> +#else
> +
> +static inline int cdns3_host_init(struct cdns3 *cdns)
> +{
> +	return -ENXIO;
> +}
> +
> +static inline void cdns3_host_remove(struct cdns3 *cdns) { }
> +static inline void cdns3_host_driver_init(void) {}
> +
> +#endif /* CONFIG_USB_CDNS3_HOST */
> +
> +#endif /* __LINUX_CDNS3_HOST_EXPORT */
> diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
> new file mode 100644
> index 000000000000..0dd47976cb28
> --- /dev/null
> +++ b/drivers/usb/cdns3/host.c
> @@ -0,0 +1,256 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver - host side
> + *
> + * Copyright (C) 2018 Cadence Design Systems.
> + * Copyright (C) 2018 NXP
> + *
> + * Authors: Peter Chen <peter.chen@nxp.com>
> + *	    Pawel Laszczak <pawell@cadence.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/usb.h>
> +#include <linux/usb/hcd.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/usb/of.h>
> +
> +#include "../host/xhci.h"
> +#include "core.h"
> +#include "host-export.h"
> +
> +static struct hc_driver __read_mostly xhci_cdns3_hc_driver;
> +
> +static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci)
> +{
> +	/*
> +	 * As of now platform drivers don't provide MSI support so we ensure
> +	 * here that the generic code does not try to make a pci_dev from our
> +	 * dev struct in order to setup MSI
> +	 */
> +	xhci->quirks |= XHCI_PLAT;
> +}
> +
> +static int xhci_cdns3_setup(struct usb_hcd *hcd)
> +{
> +	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
> +	u32 command;
> +	int ret;
> +
> +	ret = xhci_gen_setup(hcd, xhci_cdns3_quirks);
> +	if (ret)
> +		return ret;
> +
> +	/* set usbcmd.EU3S */
> +	command = readl(&xhci->op_regs->command);
> +	command |= CMD_PM_INDEX;
> +	writel(command, &xhci->op_regs->command);
> +
> +	return 0;
> +}
> +
> +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
> +	.extra_priv_size = sizeof(struct xhci_hcd),
> +	.reset = xhci_cdns3_setup,
> +};
> +
> +struct cdns3_host {
> +	struct device dev;
> +	struct usb_hcd *hcd;
> +};
> +
> +static irqreturn_t cdns3_host_irq(struct cdns3 *cdns)
> +{
> +	struct device *dev = cdns->host_dev;
> +	struct usb_hcd	*hcd;
> +
> +	if (dev)
> +		hcd = dev_get_drvdata(dev);
> +	else
> +		return IRQ_NONE;
> +
> +	if (hcd)
> +		return usb_hcd_irq(cdns->irq, hcd);
> +	else
> +		return IRQ_NONE;

Why can't you just reuse the xhci-platform driver and let it manage the IRQ?
Since it is a shared IRQ, different drivers can request the same IRQ and return IRQ_NONE
if the IRQ wasn't from their device.

> +}
> +
> +static void cdns3_host_release(struct device *dev)
> +{
> +	struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
> +
> +	kfree(host);
> +}
> +
> +static int cdns3_host_start(struct cdns3 *cdns)
> +{
> +	struct cdns3_host *host;
> +	struct device *dev;
> +	struct device *sysdev;
> +	struct xhci_hcd	*xhci;
> +	int ret;
> +
> +	host = kzalloc(sizeof(*host), GFP_KERNEL);
> +	if (!host)
> +		return -ENOMEM;
> +
> +	dev = &host->dev;
> +	dev->release = cdns3_host_release;
> +	dev->parent = cdns->dev;
> +	dev_set_name(dev, "xhci-cdns3");
> +	cdns->host_dev = dev;
> +	ret = device_register(dev);
> +	if (ret)
> +		goto err1;
> +
> +	sysdev = cdns->dev;
> +	/* Try to set 64-bit DMA first */
> +	if (WARN_ON(!sysdev->dma_mask))
> +		/* Platform did not initialize dma_mask */
> +		ret = dma_coerce_mask_and_coherent(sysdev,
> +						   DMA_BIT_MASK(64));
> +	else
> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
> +
> +	/* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
> +	if (ret) {
> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	pm_runtime_set_active(dev);
> +	pm_runtime_no_callbacks(dev);
> +	pm_runtime_enable(dev);
> +	host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
> +				     dev_name(dev), NULL);
> +	if (!host->hcd) {
> +		ret = -ENOMEM;
> +		goto err2;
> +	}
> +
> +	host->hcd->regs = cdns->xhci_regs;
> +	host->hcd->rsrc_start = cdns->xhci_res->start;
> +	host->hcd->rsrc_len = resource_size(cdns->xhci_res);
> +
> +	device_wakeup_enable(host->hcd->self.controller);
> +	xhci = hcd_to_xhci(host->hcd);
> +
> +	xhci->main_hcd = host->hcd;
> +	xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
> +					    dev_name(dev), host->hcd);
> +	if (!xhci->shared_hcd) {
> +		ret = -ENOMEM;
> +		goto err3;
> +	}
> +
> +	host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
> +	xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
> +	ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
> +	if (ret)
> +		goto err4;
> +
> +	ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
> +	if (ret)
> +		goto err5;
> +
> +	device_set_wakeup_capable(dev, true);

All this is being done by the xhci-plat.c

You can make use of it by just creating a xhci-hcd platform device.

e.g.
platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
platform_device_add_resources() to add IRQ and memory resource.
platform_device_add_properties() to add any quirks.
platform_device_add()


> +
> +	return 0;
> +
> +err5:
> +	usb_remove_hcd(host->hcd);
> +err4:
> +	usb_put_hcd(xhci->shared_hcd);
> +err3:
> +	usb_put_hcd(host->hcd);
> +err2:
> +	device_del(dev);
> +err1:
> +	put_device(dev);
> +	cdns->host_dev = NULL;
> +	return ret;
> +}
> +
> +static void cdns3_host_stop(struct cdns3 *cdns)
> +{
> +	struct device *dev = cdns->host_dev;
> +	struct xhci_hcd	*xhci;
> +	struct usb_hcd	*hcd;
> +
> +	if (dev) {
> +		hcd = dev_get_drvdata(dev);
> +		xhci = hcd_to_xhci(hcd);
> +		usb_remove_hcd(xhci->shared_hcd);
> +		usb_remove_hcd(hcd);
> +		synchronize_irq(cdns->irq);
> +		usb_put_hcd(xhci->shared_hcd);
> +		usb_put_hcd(hcd);
> +		cdns->host_dev = NULL;
> +		pm_runtime_set_suspended(dev);
> +		pm_runtime_disable(dev);
> +		device_del(dev);
> +		put_device(dev);
> +	}

You can replace this with just
	platform_device_unregister(xhci_dev);

> +}
> +
> +#if CONFIG_PM
> +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
> +{
> +	struct device *dev = cdns->host_dev;
> +	struct xhci_hcd	*xhci;
> +
> +	if (!dev)
> +		return 0;
> +
> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
> +	return xhci_suspend(xhci, do_wakeup);
> +}
> +
> +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
> +{
> +	struct device *dev = cdns->host_dev;
> +	struct xhci_hcd	*xhci;
> +
> +	if (!dev)
> +		return 0;
> +
> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
> +	return xhci_resume(xhci, hibernated);
> +}

These won't be required any more as xhci-plat is doing this.

> +#endif /* CONFIG_PM */
> +
> +int cdns3_host_init(struct cdns3 *cdns)
> +{
> +	struct cdns3_role_driver *rdrv;
> +
> +	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
> +	if (!rdrv)
> +		return -ENOMEM;
> +
> +	rdrv->start	= cdns3_host_start;
> +	rdrv->stop	= cdns3_host_stop;
> +	rdrv->irq	= cdns3_host_irq;
> +#if CONFIG_PM
> +	rdrv->suspend	= cdns3_host_suspend;
> +	rdrv->resume	= cdns3_host_resume;
> +#endif /* CONFIG_PM */
> +	rdrv->name	= "host";
> +	cdns->roles[CDNS3_ROLE_HOST] = rdrv;
> +
> +	return 0;
> +}
> +
> +void cdns3_host_remove(struct cdns3 *cdns)
> +{
> +	cdns3_host_stop(cdns);

calling cdns3_host_stop() here can lead to problems as Controller might be in
peripheral mode at this point. The core driver needs to ensure that relevant role
is stopped before calling cdns3_host_remove().

Here you need to unregister the role driver though.

cdns->roles[CDNS3_ROLE_HOST] = NULL;

> +}
> +
> +void __init cdns3_host_driver_init(void)
> +{
> +	xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
> +}
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-18 10:09 ` [RFC PATCH v2 05/15] usb:cdns3: Added DRD support Pawel Laszczak
@ 2018-11-23 14:51   ` Roger Quadros
  2018-11-26  7:23     ` Pawel Laszczak
  2018-12-04  9:18   ` Peter Chen
  1 sibling, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-23 14:51 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch adds supports for detecting Host/Device mode.
> Controller has additional OTG register that allow
> implement even whole OTG functionality.
> At this moment patch adds support only for detecting
> the appropriate mode based on strap pins and ID pin.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/Makefile |   2 +-
>  drivers/usb/cdns3/core.c   |  27 +++--
>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>  4 files changed, 372 insertions(+), 8 deletions(-)
>  create mode 100644 drivers/usb/cdns3/drd.c
>  create mode 100644 drivers/usb/cdns3/drd.h
> 
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> index 02d25b23c5d3..e779b2a2f8eb 100644
> --- a/drivers/usb/cdns3/Makefile
> +++ b/drivers/usb/cdns3/Makefile
> @@ -1,5 +1,5 @@
>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>  
> -cdns3-y					:= core.o
> +cdns3-y					:= core.o drd.o
>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> index f9055d4da67f..dbee4325da7f 100644
> --- a/drivers/usb/cdns3/core.c
> +++ b/drivers/usb/cdns3/core.c
> @@ -17,6 +17,7 @@
>  
>  #include "gadget.h"
>  #include "core.h"
> +#include "drd.h"
>  
>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>  {
> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>  {
>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> -		//TODO: implements selecting device/host mode
> -		return CDNS3_ROLE_HOST;
> +		if (cdns3_is_host(cdns))
> +			return CDNS3_ROLE_HOST;
> +		if (cdns3_is_device(cdns))
> +			return CDNS3_ROLE_GADGET;
>  	}
>  	return cdns->roles[CDNS3_ROLE_HOST]
>  		? CDNS3_ROLE_HOST
> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>  	struct cdns3 *cdns = data;
>  	irqreturn_t ret = IRQ_NONE;
>  
> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
> +		ret = cdns3_drd_irq(cdns);
> +		if (ret == IRQ_HANDLED)
> +			return ret;
> +	}

The kernel's shared IRQ model takes care of sharing the same interrupt
between different devices and their drivers. You don't need to manually
handle it here. Just let all 3 drivers do a request_irq() and have
handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
IRQ_NONE accordingly.

Looks like you can do away with irq member of the role driver struct.

> +
>  	/* Handle device/host interrupt */
>  	if (cdns->role != CDNS3_ROLE_END)
>  		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
> @@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
>  
>  	cdns = container_of(work, struct cdns3, role_switch_wq);
>  
> -	//TODO: implements this functions.
> -	//host = cdns3_is_host(cdns);
> -	//device = cdns3_is_device(cdns);
> -	host = 1;
> -	device = 0;
> +	host = cdns3_is_host(cdns);
> +	device = cdns3_is_device(cdns);

What if there is a ID transition between the 2 functions so that
and both host and device become true?
Since you are checking the ID level separately in both the functions.

How about instead having cdns3_get_id() and using
it to start/stop relevant roles if we are in OTG mode.

Is this going to be used for a role switch even if we're not in OTG mode?
If not then it is a BUG if we get here.

>  
>  	if (host)
>  		role = CDNS3_ROLE_HOST;
> @@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
>  	pm_runtime_get_sync(cdns->dev);
>  	cdns3_role_stop(cdns);
>  
> +	if (cdns->desired_dr_mode != cdns->current_dr_mode) {

This is about roles, why are we checking dr_mode here?

> +		cdns3_drd_update_mode(cdns);
> +		host = cdns3_is_host(cdns);
> +		device = cdns3_is_device(cdns);
> +	}
> +
>  	if (host) {
>  		if (cdns->roles[CDNS3_ROLE_HOST])
>  			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
> @@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
>  	if (ret)
>  		goto err2;
>  
> +	ret = cdns3_drd_init(cdns);
>  	if (ret)
>  		goto err2;
>  
> diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
> new file mode 100644
> index 000000000000..ac741c80e776
> --- /dev/null
> +++ b/drivers/usb/cdns3/drd.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver.
> + *
> + * Copyright (C) 2018 Cadence.
> + *
> + * Author: Pawel Laszczak <pawell@cadence.com
> + *
> + */
> +#include <linux/kernel.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/usb/otg.h>
> +
> +#include "gadget.h"
> +#include "drd.h"
> +
> +/**
> + * cdns3_set_mode - change mode of OTG Core
> + * @cdns: pointer to context structure
> + * @mode: selected mode from cdns_role
> + */
> +void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
> +{
> +	u32 reg;
> +
> +	cdns->current_dr_mode = mode;
> +	switch (mode) {
> +	case USB_DR_MODE_PERIPHERAL:
> +		dev_info(cdns->dev, "Set controller to Gadget mode\n");
> +		writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
> +		       &cdns->otg_regs->cmd);
> +		break;
> +	case USB_DR_MODE_HOST:
> +		dev_info(cdns->dev, "Set controller to Host mode\n");
> +		writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
> +		       &cdns->otg_regs->cmd);
> +		break;
> +	case USB_DR_MODE_OTG:
> +		dev_info(cdns->dev, "Set controller to OTG mode\n");
> +		reg = readl(&cdns->otg_regs->ctrl1);
> +		reg |= OTGCTRL1_IDPULLUP;
> +		writel(reg, &cdns->otg_regs->ctrl1);
> +
> +		/* wait until valid ID (ID_VALUE) can be sampled (50ms). */
> +		mdelay(50);
> +		break;
> +	default:
> +		cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
> +		return;
> +	}
> +}
> +
> +static int cdns3_otg_get_id(struct cdns3 *cdns)
> +{
> +	int id;
> +
> +	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
> +	dev_dbg(cdns->dev, "OTG ID: %d", id);
> +	return id;
> +}
> +
> +int cdns3_is_host(struct cdns3 *cdns)
> +{
> +	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
> +		return 1;

Why do you need this?

> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
> +		if (!cdns3_otg_get_id(cdns))
> +			return 1;
> +
> +	return 0;
> +}
> +
> +int cdns3_is_device(struct cdns3 *cdns)
> +{
> +	if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
> +		return 1;
> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
> +		if (cdns3_otg_get_id(cdns))
> +			return 1;
> +
> +	return 0;
> +}
> +
> +/**
> + * cdns3_otg_disable_irq - Disable all OTG interrupts
> + * @cdns: Pointer to controller context structure
> + */
> +static void cdns3_otg_disable_irq(struct cdns3 *cdns)
> +{
> +	writel(0, &cdns->otg_regs->ien);
> +}
> +
> +/**
> + * cdns3_otg_enable_irq - enable id and sess_valid interrupts
> + * @cdns: Pointer to controller context structure
> + */
> +static void cdns3_otg_enable_irq(struct cdns3 *cdns)
> +{
> +	writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
> +	       OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
> +}
> +
> +/**
> + * cdns3_init_otg_mode - initialize drd controller
> + * @cdns: Pointer to controller context structure
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static void cdns3_init_otg_mode(struct cdns3 *cdns)
> +{
> +	cdns3_otg_disable_irq(cdns);
> +	/* clear all interrupts */
> +	writel(~0, &cdns->otg_regs->ivect);
> +
> +	cdns3_set_mode(cdns, USB_DR_MODE_OTG);
> +
> +	cdns3_otg_enable_irq(cdns);
> +}
> +
> +/**
> + * cdns3_drd_update_mode - initialize mode of operation

Looks like this will be called only once. How about calling it

cdns3_drd_init_mode()?

> + * @cdns: Pointer to controller context structure
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +int cdns3_drd_update_mode(struct cdns3 *cdns)
> +{
> +	int ret = 0;
> +
> +	switch (cdns->desired_dr_mode) {

I think we can get rid of desired_dr_mode member in struct cdns.
Just pass the mode as an argument to cdns3_drd_init_mode()

And we already have cdns->dr_mode.

> +	case USB_DR_MODE_PERIPHERAL:
> +		cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
> +		break;
> +	case USB_DR_MODE_HOST:
> +		cdns3_set_mode(cdns, USB_DR_MODE_HOST);
> +		break;
> +	case USB_DR_MODE_OTG:
> +		cdns3_init_otg_mode(cdns);
> +		break;
> +	default:
> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n",
> +			cdns->dr_mode);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns)
> +{
> +	irqreturn_t ret = IRQ_NONE;
> +	u32 reg;
> +
> +	if (cdns->dr_mode != USB_DR_MODE_OTG)
> +		return ret;
> +
> +	reg = readl(&cdns->otg_regs->ivect);
> +	if (!reg)
> +		return ret;
> +
> +	if (reg & OTGIEN_ID_CHANGE_INT) {
> +		int id = cdns3_otg_get_id(cdns);
> +
> +		dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
> +			cdns3_otg_get_id(cdns));
> +
> +		if (id)
> +			cdns->role = CDNS3_ROLE_GADGET;
> +		else
> +			cdns->role = CDNS3_ROLE_HOST;

Why check ID and set role here? It might change by the time
the role_switch_wq starts up. Why not check the ID status there?

Also directly changing role here doesn't make sense as
there will be a mismatch between currently active role and cdns->role.

> +
> +		queue_work(system_freezable_wq, &cdns->role_switch_wq);
> +
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	writel(~0, &cdns->otg_regs->ivect);
> +	return IRQ_HANDLED;

return ret;

> +}
> +
> +int cdns3_drd_init(struct cdns3 *cdns)
> +{
> +	enum usb_dr_mode dr_mode;
> +	int ret = 0;
> +	u32 state;
> +
> +	state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts));
> +
> +	dr_mode = cdns->dr_mode;
> +	if (state == OTGSTS_STRAP_HOST) {
> +		dev_info(cdns->dev, "Controller strapped to HOST\n");
> +		dr_mode = USB_DR_MODE_HOST;
> +		if (cdns->dr_mode != USB_DR_MODE_HOST &&
> +		    cdns->dr_mode != USB_DR_MODE_OTG)
> +			ret = -EINVAL;
> +	} else if (state == OTGSTS_STRAP_GADGET) {
> +		dev_info(cdns->dev, "Controller strapped to PERIPHERAL\n");
> +		dr_mode = USB_DR_MODE_PERIPHERAL;
> +		if (cdns->dr_mode != USB_DR_MODE_PERIPHERAL &&
> +		    cdns->dr_mode != USB_DR_MODE_OTG)
> +			ret = -EINVAL;
> +	}
> +
> +	if (ret) {
> +		dev_err(cdns->dev, "Incorrect DRD configuration\n");
> +		return ret;
> +	}
> +
> +	//Updating DR mode according to strap.
> +	cdns->dr_mode = dr_mode;
> +	cdns->desired_dr_mode = dr_mode;
> +	cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
> +
> +	dev_info(cdns->dev, "Controller Device ID: %08lx, Revision ID: %08lx\n",
> +		 CDNS_RID(readl(&cdns->otg_regs->rid)),
> +		 CDNS_DID(readl(&cdns->otg_regs->did)));

dev_info should be moved at the end if cdns3_drd_update_mode() if it succeeded.

> +
> +	state = readl(&cdns->otg_regs->sts);
> +	if (OTGSTS_OTG_NRDY(state) != 0) {
> +		dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = cdns3_drd_update_mode(cdns);
> +
> +	return ret;
> +}
> diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
> new file mode 100644
> index 000000000000..0faa7520ecac
> --- /dev/null
> +++ b/drivers/usb/cdns3/drd.h
> @@ -0,0 +1,122 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence USB3 DRD part of USBSS driver
> + *
> + * Copyright (C) 2018 Cadence.
> + *
> + * Author: Pawel Laszczak <pawell@cadence.com>
> + */
> +#ifndef __LINUX_CDNS3_DRD
> +#define __LINUX_CDNS3_DRD
> +
> +#include <linux/usb/otg.h>
> +#include <linux/phy/phy.h>
> +#include "core.h"
> +
> +/*  DRD register interface. */
> +struct cdns3_otg_regs {
> +	__le32 did;
> +	__le32 rid;
> +	__le32 capabilities;
> +	__le32 reserved1;
> +	__le32 cmd;
> +	__le32 sts;
> +	__le32 state;
> +	__le32 reserved2;
> +	__le32 ien;
> +	__le32 ivect;
> +	__le32 refclk;
> +	__le32 tmr;
> +	__le32 reserved3[4];
> +	__le32 simulate;
> +	__le32 override;
> +	__le32 susp_ctrl;
> +	__le32 reserved4;
> +	__le32 anasts;
> +	__le32 adp_ramp_time;
> +	__le32 ctrl1;
> +	__le32 ctrl2;
> +};
> +
> +/* CDNS_RID - bitmasks */
> +#define CDNS_RID(p)			((p) & GENMASK(15, 0))
> +
> +/* CDNS_VID - bitmasks */
> +#define CDNS_DID(p)			((p) & GENMASK(31, 0))
> +
> +/* OTGCMD - bitmasks */
> +/* "Request the bus for Device mode. */
> +#define OTGCMD_DEV_BUS_REQ	BIT(0)
> +/* Request the bus for Host mode */
> +#define OTGCMD_HOST_BUS_REQ		BIT(1)
> +/* Enable OTG mode. */
> +#define OTGCMD_OTG_EN			BIT(2)
> +/* Disable OTG mode */
> +#define OTGCMD_OTG_DIS			BIT(3)
> +/*"Configure OTG as A-Device. */
> +#define OTGCMD_A_DEV_EN			BIT(4)
> +/*"Configure OTG as A-Device. */
> +#define OTGCMD_A_DEV_DIS		BIT(5)
> +/* Drop the bus for Device mod	e. */
> +#define OTGCMD_DEV_BUS_DROP		BIT(8)
> +/* Drop the bus for Host mode*/
> +#define OTGCMD_HOST_BUS_DROP		BIT(9)
> +/* Power Down USBSS-DEV. */
> +#define OTGCMD_DEV_POWER_OFF		BIT(11)
> +/* Power Down CDNSXHCI. */
> +#define OTGCMD_HOST_POWER_OFF		BIT(12)
> +
> +/* OTGIEN - bitmasks */
> +/* ID change interrupt enable */
> +#define OTGIEN_ID_CHANGE_INT		BIT(0)
> +/* Vbusvalid fall detected interrupt enable.*/
> +#define OTGIEN_VBUSVALID_RISE_INT	BIT(4)
> +/* Vbusvalid fall detected interrupt enable */
> +#define OTGIEN_VBUSVALID_FALL_INT	BIT(5)
> +
> +/* OTGSTS - bitmasks */
> +/*
> + * Current value of the ID pin. It is only valid when idpullup in
> + *  OTGCTRL1_TYPE register is set to '1'.
> + */
> +#define OTGSTS_ID_VALUE			BIT(0)
> +/* Current value of the vbus_valid */
> +#define OTGSTS_VBUS_VALID		BIT(1)
> +/* Current value of the b_sess_vld */
> +#define OTGSTS_SESSION_VALID		BIT(2)
> +/*Device mode is active*/
> +#define OTGSTS_DEV_ACTIVE		BIT(3)
> +/* Host mode is active. */
> +#define OTGSTS_HOST_ACTIVE		BIT(4)
> +/* OTG Controller not ready. */
> +#define OTGSTS_OTG_NRDY_MASK		BIT(11)
> +#define OTGSTS_OTG_NRDY(p)		((p) & OTGSTS_OTG_NRDY_MASK)
> +/*
> + * Value of the strap pins.
> + * 000 - no default configuration
> + * 010 - Controller initiall configured as Host
> + * 100 - Controller initially configured as Device
> + */
> +#define OTGSTS_STRAP(p)			(((p) & GENMASK(14, 12)) >> 12)
> +#define OTGSTS_STRAP_NO_DEFAULT_CFG	0x00
> +#define OTGSTS_STRAP_HOST_OTG		0x01
> +#define OTGSTS_STRAP_HOST		0x02
> +#define OTGSTS_STRAP_GADGET		0x04
> +/* Host mode is turned on. */
> +#define OTGSTSE_XHCI_READYF		BIT(26)
> +/* "Device mode is turned on .*/
> +#define OTGSTS_DEV_READY		BIT(27)
> +
> +/* OTGREFCLK - bitmasks */
> +#define OTGREFCLK_STB_CLK_SWITCH_EN	BIT(31)
> +
> +/* OTGCTRL1 - bitmasks */
> +#define OTGCTRL1_IDPULLUP		BIT(24)
> +
> +int cdns3_is_host(struct cdns3 *cdns);
> +int cdns3_is_device(struct cdns3 *cdns);
> +int cdns3_drd_init(struct cdns3 *cdns);
> +int cdns3_drd_update_mode(struct cdns3 *cdns);
> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns);
> +
> +#endif /* __LINUX_CDNS3_DRD */
> 

cheers,
-roger
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller.
  2018-11-23 10:53   ` Roger Quadros
@ 2018-11-25  7:33     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-25  7:33 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi Roger

>On 18/11/18 12:08, Pawel Laszczak wrote:
>> Thsi patch aim at documenting USB related dt-bindings for the
>
>s/Thsi/This
>
>> Cadence USBSS-DRD controller.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  .../devicetree/bindings/usb/cdns3-usb.txt       | 17 +++++++++++++++++
>>  1 file changed, 17 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/usb/cdns3-usb.txt
>>
>> diff --git a/Documentation/devicetree/bindings/usb/cdns3-usb.txt b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
>> new file mode 100644
>> index 000000000000..f9e953f32d47
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
>> @@ -0,0 +1,17 @@
>> +Binding for the Cadence USBSS-DRD controller
>> +
>> +Required properties:
>> +  - reg: Physical base address and size of the controller's register area.
>> +  - compatible: Should contain: "cdns,usb3"
>> +  - interrupts: Interrupt specifier. Refer to interrupt bindings.
>
>Do you also comply with USB generic bindings for dr_mode, speed, etc?
>i.e. Documentation/devicetree/bindings/usb/generic.txt
>
>If yes it must be mentioned in Optional properties:

I took a look at this document and I think that only dr_mode and maximum-speed make sense 
as optional parameters.
I will add these two as optional. 

Thanks.

>
>> +
>> +
>> +Example:
>> +	cdns3@f3000000 {
>> +		compatible = "cdns,usb3";
>> +		interrupts = <USB_IRQ  7 IRQ_TYPE_LEVEL_HIGH>;
>> +		reg = <0xf3000000 0x10000	//memory area for OTG/DRD registers
>> +			0xf3010000 0x10000	//memory area for HOST registers
>> +			0xf3020000 0x10000>;	//memory area for Device registers
>> +	};
>> +
>>
>
>cheers,
>-roger
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

Cheers,
Pawel

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-23 11:35   ` Roger Quadros
@ 2018-11-25 12:35     ` Pawel Laszczak
  2018-12-04  9:09       ` Peter Chen
  2018-12-04  8:50     ` Peter Chen
  1 sibling, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-25 12:35 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi Roger

>On 18/11/18 12:09, Pawel Laszczak wrote:
>> Patch adds core.c and core.h file that implements initialization
>> of platform driver and adds function responsible for selecting,
>> switching and running appropriate Device/Host mode.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/Makefile |   2 +
>>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
>>  drivers/usb/cdns3/core.h   | 100 +++++++++
>>  3 files changed, 515 insertions(+)
>>  create mode 100644 drivers/usb/cdns3/core.c
>>  create mode 100644 drivers/usb/cdns3/core.h
>>
>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> index dcdd62003c6a..02d25b23c5d3 100644
>> --- a/drivers/usb/cdns3/Makefile
>> +++ b/drivers/usb/cdns3/Makefile
>> @@ -1,3 +1,5 @@
>> +obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>
>> +cdns3-y					:= core.o
>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> new file mode 100644
>> index 000000000000..f9055d4da67f
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/core.c
>> @@ -0,0 +1,413 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver.
>> + *
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Author: Peter Chen <peter.chen@nxp.com>
>> + *         Pawel Laszczak <pawell@cadence.com>
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "gadget.h"
>> +#include "core.h"
>> +
>> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>> +{
>> +	WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
>> +	return cdns->roles[cdns->role];
>> +}
>> +
>> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
>> +{
>> +	int ret;
>> +
>> +	if (role >= CDNS3_ROLE_END)
>
>WARN_ON()?
I agree. 
>
>> +		return 0;
>> +
>> +	if (!cdns->roles[role])
>> +		return -ENXIO;
>> +
>> +	mutex_lock(&cdns->mutex);
>> +	cdns->role = role;
>> +	ret = cdns->roles[role]->start(cdns);
>> +	mutex_unlock(&cdns->mutex);
>> +	return ret;
>> +}
>> +
>> +static inline void cdns3_role_stop(struct cdns3 *cdns)
>> +{
>> +	enum cdns3_roles role = cdns->role;
>> +
>> +	if (role == CDNS3_ROLE_END)
>
>WARN_ON(role >= CNDS3_ROLE_END) ?
I agree
>
>> +		return;
>> +
>> +	mutex_lock(&cdns->mutex);
>> +	cdns->roles[role]->stop(cdns);
>> +	cdns->role = CDNS3_ROLE_END;
>
>Why change the role here? You are just stopping the role not changing it.
>I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>if required without error.

This line is unnecessary.

>
>> +	mutex_unlock(&cdns->mutex);
>> +}
>> +
>> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>> +{
>> +	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> +		//TODO: implements selecting device/host mode
>> +		return CDNS3_ROLE_HOST;
>> +	}
>> +	return cdns->roles[CDNS3_ROLE_HOST]
>> +		? CDNS3_ROLE_HOST
>> +		: CDNS3_ROLE_GADGET;
>
>Why not just
>	return cdns->role;
>
>I'm wondering if we really need this function

TODO will look likie:
		if (cdns3_is_host(cdns))
			return CDNS3_ROLE_HOST;
		if (cdns3_is_device(cdns))
			return CDNS3_ROLE_GADGET;

Function selects initial role. Before invoking it the role is unknown.
I think that function name should be  changed because current name can be misleading. 

I will change it to cdns3_get_initial_role.
.
>> +}
>
>> +
>> +/**
>> + * cdns3_core_init_role - initialize role of operation
>> + * @cdns: Pointer to cdns3 structure
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static int cdns3_core_init_role(struct cdns3 *cdns)
>> +{
>> +	struct device *dev = cdns->dev;
>> +	enum usb_dr_mode dr_mode;
>> +
>> +	dr_mode = usb_get_dr_mode(dev);
>> +	cdns->role = CDNS3_ROLE_END;
>> +
>> +	/*
>> +	 * If driver can't read mode by means of usb_get_dr_mdoe function then
>> +	 * chooses mode according with Kernel configuration. This setting
>> +	 * can be restricted later depending on strap pin configuration.
>> +	 */
>> +	if (dr_mode == USB_DR_MODE_UNKNOWN) {
>> +		if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
>> +		    IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> +			dr_mode = USB_DR_MODE_OTG;
>> +		else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
>> +			dr_mode = USB_DR_MODE_HOST;
>> +		else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> +			dr_mode = USB_DR_MODE_PERIPHERAL;
>> +	}
>> +
>> +	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>> +		//TODO: implements host initialization
>
>		/* TODO: Add host role */ ?
>
>> +	}
>> +
>> +	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>> +		//TODO: implements device initialization
>
>		/* TODO: Add device role */ ?
>
>> +	}
>> +
>> +	if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>> +		dev_err(dev, "no supported roles\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	cdns->dr_mode = dr_mode;
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_irq - interrupt handler for cdns3 core device
>> + *
>> + * @irq: irq number for cdns3 core device
>> + * @data: structure of cdns3
>> + *
>> + * Returns IRQ_HANDLED or IRQ_NONE
>> + */
>> +static irqreturn_t cdns3_irq(int irq, void *data)
>> +{
>> +	struct cdns3 *cdns = data;
>> +	irqreturn_t ret = IRQ_NONE;
>> +
>> +	/* Handle device/host interrupt */
>> +	if (cdns->role != CDNS3_ROLE_END)
>
>Is it because of this that you need to set role to END at role_stop?
>I think it is better to add a state variable to struct cdns3_role_driver, so we can
>check if it is active or stopped.
>
>e.g.
>	if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)

Ok, I will do it in this way. 
>> +		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>> +
>> +	return ret;
>> +}
>> +
>> +static void cdns3_remove_roles(struct cdns3 *cdns)
>
>Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?

Sounds better. 
I also change cdns3_host_remove to cdns3_host_exit and
cdns3_gadget_remove to cdns3_gadget_exit. 
>
>> +{
>> +	//TODO: implements this function
>> +}
>
>> +
>> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>> +{
>> +	enum cdns3_roles current_role;
>> +	int ret = 0;
>> +
>> +	current_role = cdns->role;
>> +
>> +	if (role == CDNS3_ROLE_END)
>> +		return 0;
>
>role == END looks like error state. and it should never happen.
>WARN here?

Ok, will be changed.
>
>> +
>> +	dev_dbg(cdns->dev, "Switching role");
>> +
>
>Don't you have to stop the previous role before starting the new role?
>
>> +	ret = cdns3_role_start(cdns, role);
>> +	if (ret) {
>> +		/* Back to current role */
>> +		dev_err(cdns->dev, "set %d has failed, back to %d\n",
>> +			role, current_role);
>> +		ret = cdns3_role_start(cdns, current_role);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_role_switch - work queue handler for role switch
>> + *
>> + * @work: work queue item structure
>> + *
>> + * Handles below events:
>> + * - Role switch for dual-role devices
>> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
>> + */
>> +static void cdns3_role_switch(struct work_struct *work)
>> +{
>> +	enum cdns3_roles role = CDNS3_ROLE_END;
>> +	struct cdns3 *cdns;
>> +	bool device, host;
>> +
>> +	cdns = container_of(work, struct cdns3, role_switch_wq);
>> +
>> +	//TODO: implements this functions.
>> +	//host = cdns3_is_host(cdns);
>> +	//device = cdns3_is_device(cdns);
>> +	host = 1;
>> +	device = 0;
>> +
>> +	if (host)
>> +		role = CDNS3_ROLE_HOST;
>> +	else if (device)
>> +		role = CDNS3_ROLE_GADGET;
>> +
>> +	if (cdns->desired_dr_mode == cdns->current_dr_mode &&
>> +	    cdns->role == role)
>> +		return;
>> +
>
>I think all the below code can be moved to cdns3_do_role_switch().

Yes, I agree with you. cdns3_role_stop  should be in cdns3_do_role_switch. 

>> +	pm_runtime_get_sync(cdns->dev);
>> +	cdns3_role_stop(cdns);
>> +
>> +	if (host) {
>> +		if (cdns->roles[CDNS3_ROLE_HOST])
>> +			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>> +		pm_runtime_put_sync(cdns->dev);
>> +		return;
>> +	}
>> +
>> +	if (device)
>> +		cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
>> +	else
>> +		cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
>> +
>> +	pm_runtime_put_sync(cdns->dev);
>> +}
>> +
>> +/**
>> + * cdns3_probe - probe for cdns3 core device
>> + * @pdev: Pointer to cdns3 core platform device
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static int cdns3_probe(struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct resource	*res;
>> +	struct cdns3 *cdns;
>> +	void __iomem *regs;
>> +	int ret;
>> +
>> +	cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
>> +	if (!cdns)
>> +		return -ENOMEM;
>> +
>> +	cdns->dev = dev;
>> +
>> +	platform_set_drvdata(pdev, cdns);
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
>> +	if (!res) {
>> +		dev_err(dev, "missing IRQ\n");
>> +		return -ENODEV;
>> +	}
>> +	cdns->irq = res->start;
>> +
>> +	/*
>> +	 * Request memory region
>> +	 * region-0: xHCI
>> +	 * region-1: Peripheral
>> +	 * region-2: OTG registers
>> +	 */
>
>The memory region order is different from the dt-binding.
>There it is OTG, host(xhci), device (peripheral).

I corrected dt-binding.
>
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	regs = devm_ioremap_resource(dev, res);
>> +
>> +	if (IS_ERR(regs))
>> +		return PTR_ERR(regs);
>> +	cdns->xhci_regs = regs;
>> +	cdns->xhci_res = res;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> +	regs = devm_ioremap_resource(dev, res);
>> +	if (IS_ERR(regs))
>> +		return PTR_ERR(regs);
>> +	cdns->dev_regs	= regs;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>> +	regs = devm_ioremap_resource(dev, res);
>> +	if (IS_ERR(regs))
>> +		return PTR_ERR(regs);
>> +	cdns->otg_regs = regs;
>> +
>> +	mutex_init(&cdns->mutex);
>> +
>> +	cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>
>"cdns3,usbphy" is not documented in dt-binding.

I assume that I should add  to dt-binding (cdns3-usb.txt) something like:
 - phys: reference to the USB PHY
 - phy-names: name of the USB PHY, should be " cdns3,usbphy "
 
>
>> +	if (IS_ERR(cdns->phy)) {
>> +		dev_info(dev, "no generic phy found\n");
>> +		cdns->phy = NULL;
>> +		/*
>> +		 * fall through here!
>> +		 * if no generic phy found, phy init
>> +		 * should be done under boot!
>> +		 */
>
>No you shouldn't fall through always if it is an error condition.
>Something like this should work better.
>
>        if (IS_ERR(cnds->phy)) {
>                ret = PTR_ERR(cdns->phy);
>                if (ret == -ENOSYS || ret == -ENODEV) {
>                        cdns->phy = NULL;
>                } else if (ret == -EPROBE_DEFER) {
>                        return ret;
>                } else {
>                        dev_err(dev, "no phy found\n");
>                        goto err0;
>                }
>        }
>
>So if PHY was provided in DT, and PHY support/drivers is present
>and error condition means something is wrong and we have to error out.
>
>> +	} else {
>> +		phy_init(cdns->phy);
>> +	}
>
>You can do phy_init() outside the else.
>
Thank you for explanation. I will correct this. 
>> +
>> +	ret = cdns3_core_init_role(cdns);
>> +	if (ret)
>> +		goto err1;
>> +
>> +	INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
>> +	if (ret)
>> +		goto err2;
>> +
>> +	if (ret)
>> +		goto err2;
>> +
>> +	cdns->role = cdns3_get_role(cdns);
>
>I think this should move to cd I'll have a some though on ns3_core_init_role().

Ok, I will do it. 
>
>> +
>> +	ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
>> +			       dev_name(dev), cdns);
>> +
>> +	if (ret)
>> +		goto err2;
>
>How about moving request_irq to before cdsn3_core_init_role()?
>
>Then you can move cdns3_role_start() as well to core_init_role().
I'll give it  some though on it, but probably I will probably have to change little other function..
So the new order should look like this: 

cdns3_drd_init
devm_request_irq
cdns3_core_init_role, cdns3_get_role, cdns3_role_start

>
>> +
>> +	ret = cdns3_role_start(cdns, cdns->role);
>> +	if (ret) {
>> +		dev_err(dev, "can't start %s role\n",
>> +			cdns3_get_current_role_driver(cdns)->name);
>> +		goto err2;
>> +	}
>> +
>> +	device_set_wakeup_capable(dev, true);
>> +	pm_runtime_set_active(dev);
>> +	pm_runtime_enable(dev);
>> +
>> +	/*
>> +	 * The controller needs less time between bus and controller suspend,
>> +	 * and we also needs a small delay to avoid frequently entering low
>> +	 * power mode.
>> +	 */
>> +	pm_runtime_set_autosuspend_delay(dev, 20);
>> +	pm_runtime_mark_last_busy(dev);
>> +	pm_runtime_use_autosuspend(dev);
>> +	dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
>> +
>> +	return 0;
>> +
>> +err2:
>> +	cdns3_remove_roles(cdns);
>> +err1:
>
>phy_exit() ?
I will add. 
>
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_remove - unbind drd driver and clean up
>> + * @pdev: Pointer to Linux platform device
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static int cdns3_remove(struct platform_device *pdev)
>> +{
>> +	struct cdns3 *cdns = platform_get_drvdata(pdev);
>> +
>> +	pm_runtime_get_sync(&pdev->dev);
>> +	pm_runtime_disable(&pdev->dev);
>> +	pm_runtime_put_noidle(&pdev->dev);
>> +	cdns3_remove_roles(cdns);
>
>phy_exit() ?
I will add.
>
>> +
>> +	return 0;
>> +}
>> +
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id of_cdns3_match[] = {
>> +	{ .compatible = "cdns,usb3" },
>> +	{ },
>> +};
>> +MODULE_DEVICE_TABLE(of, of_cdns3_match);
>> +#endif
>> +
>> +#ifdef CONFIG_PM
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int cdns3_suspend(struct device *dev)
>> +{
>> +	//TODO: implements this function
>> +	return 0;
>> +}
>> +
>> +static int cdns3_resume(struct device *dev)
>> +{
>> +	//TODO: implements this function
>> +	return 0;
>> +}
>> +#endif /* CONFIG_PM_SLEEP */
>> +static int cdns3_runtime_suspend(struct device *dev)
>> +{	//TODO: implements this function
>> +	return 0;
>> +}
>> +
>> +static int cdns3_runtime_resume(struct device *dev)
>> +{
>> +	//TODO: implements this function
>> +	return 0;
>> +}
>> +#endif /* CONFIG_PM */
>> +
>> +static const struct dev_pm_ops cdns3_pm_ops = {
>> +	SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
>> +	SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
>> +};
>> +
>> +static struct platform_driver cdns3_driver = {
>> +	.probe		= cdns3_probe,
>> +	.remove		= cdns3_remove,
>> +	.driver		= {
>> +		.name	= "cdns-usb3",
>> +		.of_match_table	= of_match_ptr(of_cdns3_match),
>> +		.pm	= &cdns3_pm_ops,
>> +	},
>> +};
>> +
>> +static int __init cdns3_driver_platform_register(void)
>> +{
>> +	return platform_driver_register(&cdns3_driver);
>> +}
>> +module_init(cdns3_driver_platform_register);
>> +
>> +static void __exit cdns3_driver_platform_unregister(void)
>> +{
>> +	platform_driver_unregister(&cdns3_driver);
>> +}
>> +module_exit(cdns3_driver_platform_unregister);
>> +
>> +MODULE_ALIAS("platform:cdns3");
>> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
>> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
>> new file mode 100644
>> index 000000000000..7c8204fe4d3d
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/core.h
>> @@ -0,0 +1,100 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Cadence USBSS DRD Driver.
>> + *
>> + * Copyright (C) 2017 NXP
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Authors: Peter Chen <peter.chen@nxp.com>
>> + *          Pawel Laszczak <pawell@cadence.com>
>> + */
>> +#include <linux/usb/otg.h>
>> +
>> +#ifndef __LINUX_CDNS3_CORE_H
>> +#define __LINUX_CDNS3_CORE_H
>> +
>> +struct cdns3;
>> +enum cdns3_roles {
>> +	CDNS3_ROLE_HOST = 0,
>> +	CDNS3_ROLE_GADGET,
>> +	CDNS3_ROLE_END,
>> +};
>> +
>> +/**
>> + * struct cdns3_role_driver - host/gadget role driver
>> + * @start: start this role
>> + * @stop: stop this role
>> + * @suspend: suspend callback for this role
>> + * @resume: resume callback for this role
>> + * @irq: irq handler for this role
>> + * @name: role name string (host/gadget)
>> + */
>> +struct cdns3_role_driver {
>> +	int (*start)(struct cdns3 *cdns);
>> +	void (*stop)(struct cdns3 *cdns);
>> +	int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
>> +	int (*resume)(struct cdns3 *cdns, bool hibernated);
>> +	irqreturn_t (*irq)(struct cdns3 *cdns);
>> +	const char *name;
>> +};
>> +
>> +#define CDNS3_NUM_OF_CLKS	5
>> +/**
>> + * struct cdns3 - Representation of Cadence USB3 DRD controller.
>> + * @dev: pointer to Cadence device struct
>> + * @xhci_regs: pointer to base of xhci registers
>> + * @xhci_res: the resource for xhci
>> + * @dev_regs: pointer to base of dev registers
>> + * @otg_regs: pointer to base of otg registers
>> + * @irq: irq number for controller
>> + * @roles: array of supported roles for this controller
>> + * @role: current role
>> + * @host_dev: the child host device pointer for cdns3 core
>> + * @gadget_dev: the child gadget device pointer for cdns3 core
>> + * @usb: phy for this controller
>> + * @role_switch_wq: work queue item for role switch
>> + * @in_lpm: the controller in low power mode
>> + * @wakeup_int: the wakeup interrupt
>> + * @mutex: the mutex for concurrent code at driver
>> + * @dr_mode: supported mode of operation it can be only Host, only Device
>> + *           or OTG mode that allow to switch between Device and Host mode.
>> + *           This field based on hardware configuration and cant't be changed.
>
>But dr_mode can be forced in device-tree. So it isn't really only hardware configuration.
Right, I added dr_mode to dt-binding , so we have STRAP bits in registers and additionally 
optional dr_mode in device-tree. Driver should take into account this two options.   
I will remove this line. 

>
>> + * @current_dr_role: current mode of operation when in dual-role mode
>> + * @desired_dr_role: desired mode of operation when in dual-role mode.
>> + *           This value can be changed during runtime.
>> + *           Available options depends on  dr_mode:
>> + *           dr_mode                 |  desired_dr_role and current_dr_role
>> + *           ----------------------------------------------------------------
>> + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
>> + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
>> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
>> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
>> + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
>
>Do you need to update the right hand side to reflect ROLEs instead of MODE?

I see that there are incorrect name. There should be mode instead role. 
In structure below the names are correct. 
>
>> + *
>> + *           Desired_dr_role can be changed by means of debugfs.
>> + * @root: debugfs root folder pointer
>> + */
>> +struct cdns3 {
>> +	struct device			*dev;
>> +	void __iomem			*xhci_regs;
>> +	struct resource			*xhci_res;
>> +	struct cdns3_usb_regs __iomem	*dev_regs;
>> +	struct cdns3_otg_regs		*otg_regs;
>> +	int irq;
>> +	struct cdns3_role_driver	*roles[CDNS3_ROLE_END];
>> +	enum cdns3_roles		role;
>> +	struct device			*host_dev;
>> +	struct device			*gadget_dev;
>> +	struct phy			*phy;
>> +	struct work_struct		role_switch_wq;
>> +	int				in_lpm:1;
>> +	int				wakeup_int:1;
>> +	/* mutext used in workqueue*/
>> +	struct mutex			mutex;
>> +	enum usb_dr_mode		dr_mode;
>> +	enum usb_dr_mode		current_dr_mode;
>> +	enum usb_dr_mode		desired_dr_mode;
>> +	struct dentry			*root;
>> +};
>> +
>> +#endif /* __LINUX_CDNS3_CORE_H */
>>
>
>cheers,
>-roger
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

Thank for all your comments,
Cheers,
Pawel

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

* RE: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-23 14:51   ` Roger Quadros
@ 2018-11-26  7:23     ` Pawel Laszczak
  2018-11-26  8:07       ` Roger Quadros
  2018-11-27 11:29       ` Pawel Laszczak
  0 siblings, 2 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-26  7:23 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi Roger,

>On 18/11/18 12:09, Pawel Laszczak wrote:
>> Patch adds supports for detecting Host/Device mode.
>> Controller has additional OTG register that allow
>> implement even whole OTG functionality.
>> At this moment patch adds support only for detecting
>> the appropriate mode based on strap pins and ID pin.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/Makefile |   2 +-
>>  drivers/usb/cdns3/core.c   |  27 +++--
>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>  create mode 100644 drivers/usb/cdns3/drd.c
>>  create mode 100644 drivers/usb/cdns3/drd.h
>>
>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> index 02d25b23c5d3..e779b2a2f8eb 100644
>> --- a/drivers/usb/cdns3/Makefile
>> +++ b/drivers/usb/cdns3/Makefile
>> @@ -1,5 +1,5 @@
>>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>
>> -cdns3-y					:= core.o
>> +cdns3-y					:= core.o drd.o
>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> index f9055d4da67f..dbee4325da7f 100644
>> --- a/drivers/usb/cdns3/core.c
>> +++ b/drivers/usb/cdns3/core.c
>> @@ -17,6 +17,7 @@
>>
>>  #include "gadget.h"
>>  #include "core.h"
>> +#include "drd.h"
>>
>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>  {
>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>  {
>>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> -		//TODO: implements selecting device/host mode
>> -		return CDNS3_ROLE_HOST;
>> +		if (cdns3_is_host(cdns))
>> +			return CDNS3_ROLE_HOST;
>> +		if (cdns3_is_device(cdns))
>> +			return CDNS3_ROLE_GADGET;
>>  	}
>>  	return cdns->roles[CDNS3_ROLE_HOST]
>>  		? CDNS3_ROLE_HOST
>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>  	struct cdns3 *cdns = data;
>>  	irqreturn_t ret = IRQ_NONE;
>>
>> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
>> +		ret = cdns3_drd_irq(cdns);
>> +		if (ret == IRQ_HANDLED)
>> +			return ret;
>> +	}
>
>The kernel's shared IRQ model takes care of sharing the same interrupt
>between different devices and their drivers. You don't need to manually
>handle it here. Just let all 3 drivers do a request_irq() and have
>handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
>IRQ_NONE accordingly.
>
>Looks like you can do away with irq member of the role driver struct.

Ok, I will split it into 3 separate part, but in this case, I additionally have to check the current 
role in ISR function. Driver can't read host side registers when controller works in device role 
and vice versa. One part of controller is kept in reset. Only DRD registers are common and are all accessible.

>> +
>>  	/* Handle device/host interrupt */
>>  	if (cdns->role != CDNS3_ROLE_END)
>>  		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>> @@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
>>
>>  	cdns = container_of(work, struct cdns3, role_switch_wq);
>>
>> -	//TODO: implements this functions.
>> -	//host = cdns3_is_host(cdns);
>> -	//device = cdns3_is_device(cdns);
>> -	host = 1;
>> -	device = 0;
>> +	host = cdns3_is_host(cdns);
>> +	device = cdns3_is_device(cdns);
>
>What if there is a ID transition between the 2 functions so that
>and both host and device become true?
>Since you are checking the ID level separately in both the functions.
>
>How about instead having cdns3_get_id() and using
>it to start/stop relevant roles if we are in OTG mode.
>
>Is this going to be used for a role switch even if we're not in OTG mode?
>If not then it is a BUG if we get here.
>
Good point.
User can change current mode by debugfs and then this function will also invoked.
Probably I use  cdns3_get_id as you suggest. 

>>
>>  	if (host)
>>  		role = CDNS3_ROLE_HOST;
>> @@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
>>  	pm_runtime_get_sync(cdns->dev);
>>  	cdns3_role_stop(cdns);
>>
>> +	if (cdns->desired_dr_mode != cdns->current_dr_mode) {
>
>This is about roles, why are we checking dr_mode here?

Because after changing dr_mode by means of debugfs we need to update mode. 
Driver should do this after stopping the previous role.  I will move this condition 
to cdns3_drd_update_mode and add comment in this place. 

>
>> +		cdns3_drd_update_mode(cdns);
>> +		host = cdns3_is_host(cdns);
>> +		device = cdns3_is_device(cdns);
>> +	}
>> +
>>  	if (host) {
>>  		if (cdns->roles[CDNS3_ROLE_HOST])
>>  			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>> @@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
>>  	if (ret)
>>  		goto err2;
>>
>> +	ret = cdns3_drd_init(cdns);
>>  	if (ret)
>>  		goto err2;
>>
>> diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
>> new file mode 100644
>> index 000000000000..ac741c80e776
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/drd.c
>> @@ -0,0 +1,229 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver.
>> + *
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Author: Pawel Laszczak <pawell@cadence.com
>> + *
>> + */
>> +#include <linux/kernel.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/usb/otg.h>
>> +
>> +#include "gadget.h"
>> +#include "drd.h"
>> +
>> +/**
>> + * cdns3_set_mode - change mode of OTG Core
>> + * @cdns: pointer to context structure
>> + * @mode: selected mode from cdns_role
>> + */
>> +void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
>> +{
>> +	u32 reg;
>> +
>> +	cdns->current_dr_mode = mode;
>> +	switch (mode) {
>> +	case USB_DR_MODE_PERIPHERAL:
>> +		dev_info(cdns->dev, "Set controller to Gadget mode\n");
>> +		writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
>> +		       &cdns->otg_regs->cmd);
>> +		break;
>> +	case USB_DR_MODE_HOST:
>> +		dev_info(cdns->dev, "Set controller to Host mode\n");
>> +		writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
>> +		       &cdns->otg_regs->cmd);
>> +		break;
>> +	case USB_DR_MODE_OTG:
>> +		dev_info(cdns->dev, "Set controller to OTG mode\n");
>> +		reg = readl(&cdns->otg_regs->ctrl1);
>> +		reg |= OTGCTRL1_IDPULLUP;
>> +		writel(reg, &cdns->otg_regs->ctrl1);
>> +
>> +		/* wait until valid ID (ID_VALUE) can be sampled (50ms). */
>> +		mdelay(50);
>> +		break;
>> +	default:
>> +		cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
>> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
>> +		return;
>> +	}
>> +}
>> +
>> +static int cdns3_otg_get_id(struct cdns3 *cdns)
>> +{
>> +	int id;
>> +
>> +	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
>> +	dev_dbg(cdns->dev, "OTG ID: %d", id);
>> +	return id;
>> +}
>> +
>> +int cdns3_is_host(struct cdns3 *cdns)
>> +{
>> +	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
>> +		return 1;
>
>Why do you need this?

I assumed that some SoC could have cut DRD /OTG and Device or Host part. 
In such case the driver cannot be based on ID pin. 
For only HOST it's not a problem because 
the standard XHCI driver will be used.  Probably I will remove this fragment.
>
>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>> +		if (!cdns3_otg_get_id(cdns))
>> +			return 1;
>> +
>> +	return 0;
>> +}
>> +
>> +int cdns3_is_device(struct cdns3 *cdns)
>> +{
>> +	if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
>> +		return 1;
>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>> +		if (cdns3_otg_get_id(cdns))
>> +			return 1;
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_otg_disable_irq - Disable all OTG interrupts
>> + * @cdns: Pointer to controller context structure
>> + */
>> +static void cdns3_otg_disable_irq(struct cdns3 *cdns)
>> +{
>> +	writel(0, &cdns->otg_regs->ien);
>> +}
>> +
>> +/**
>> + * cdns3_otg_enable_irq - enable id and sess_valid interrupts
>> + * @cdns: Pointer to controller context structure
>> + */
>> +static void cdns3_otg_enable_irq(struct cdns3 *cdns)
>> +{
>> +	writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
>> +	       OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
>> +}
>> +
>> +/**
>> + * cdns3_init_otg_mode - initialize drd controller
>> + * @cdns: Pointer to controller context structure
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static void cdns3_init_otg_mode(struct cdns3 *cdns)
>> +{
>> +	cdns3_otg_disable_irq(cdns);
>> +	/* clear all interrupts */
>> +	writel(~0, &cdns->otg_regs->ivect);
>> +
>> +	cdns3_set_mode(cdns, USB_DR_MODE_OTG);
>> +
>> +	cdns3_otg_enable_irq(cdns);
>> +}
>> +
>> +/**
>> + * cdns3_drd_update_mode - initialize mode of operation
>
>Looks like this will be called only once. How about calling it
>
>cdns3_drd_init_mode()?

It will be also called after changing dr_mode from debugfs.

>> + * @cdns: Pointer to controller context structure
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +int cdns3_drd_update_mode(struct cdns3 *cdns)
>> +{
>> +	int ret = 0;
>> +
>> +	switch (cdns->desired_dr_mode) {
>
>I think we can get rid of desired_dr_mode member in struct cdns.
>Just pass the mode as an argument to cdns3_drd_init_mode()

This will be used also in patch that introduce debugfs. This filed is also used 
during changing dr_mode from user space.

My intention was:
dr_mode - indicated what driver can support, this filed based on dr_mode from device tree, 
	     straps bits from otg register and kernel configuration. 
desired_ dr_mode - the next mode desired by user, changed by debugfs 
current_dr_mode  - actually selected mode 

>
>And we already have cdns->dr_mode.
>
>> +	case USB_DR_MODE_PERIPHERAL:
>> +		cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
>> +		break;
>> +	case USB_DR_MODE_HOST:
>> +		cdns3_set_mode(cdns, USB_DR_MODE_HOST);
>> +		break;
>> +	case USB_DR_MODE_OTG:
>> +		cdns3_init_otg_mode(cdns);
>> +		break;
>> +	default:
>> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n",
>> +			cdns->dr_mode);
>> +		return -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns)
>> +{
>> +	irqreturn_t ret = IRQ_NONE;
>> +	u32 reg;
>> +
>> +	if (cdns->dr_mode != USB_DR_MODE_OTG)
>> +		return ret;
>> +
>> +	reg = readl(&cdns->otg_regs->ivect);
>> +	if (!reg)
>> +		return ret;
>> +
>> +	if (reg & OTGIEN_ID_CHANGE_INT) {
>> +		int id = cdns3_otg_get_id(cdns);
>> +
>> +		dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
>> +			cdns3_otg_get_id(cdns));
>> +
>> +		if (id)
>> +			cdns->role = CDNS3_ROLE_GADGET;
>> +		else
>> +			cdns->role = CDNS3_ROLE_HOST;
>
>Why check ID and set role here? It might change by the time
>the role_switch_wq starts up. Why not check the ID status there?
>
>Also directly changing role here doesn't make sense as
>there will be a mismatch between currently active role and cdns->role.

Yes this fragment does not make sense.  
>> +
>> +		queue_work(system_freezable_wq, &cdns->role_switch_wq);
>> +
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>> +	writel(~0, &cdns->otg_regs->ivect);
>> +	return IRQ_HANDLED;
>
>return ret;
>
>> +}
>> +
>> +int cdns3_drd_init(struct cdns3 *cdns)
>> +{
>> +	enum usb_dr_mode dr_mode;
>> +	int ret = 0;
>> +	u32 state;
>> +
>> +	state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts));
>> +
>> +	dr_mode = cdns->dr_mode;
>> +	if (state == OTGSTS_STRAP_HOST) {
>> +		dev_info(cdns->dev, "Controller strapped to HOST\n");
>> +		dr_mode = USB_DR_MODE_HOST;
>> +		if (cdns->dr_mode != USB_DR_MODE_HOST &&
>> +		    cdns->dr_mode != USB_DR_MODE_OTG)
>> +			ret = -EINVAL;
>> +	} else if (state == OTGSTS_STRAP_GADGET) {
>> +		dev_info(cdns->dev, "Controller strapped to PERIPHERAL\n");
>> +		dr_mode = USB_DR_MODE_PERIPHERAL;
>> +		if (cdns->dr_mode != USB_DR_MODE_PERIPHERAL &&
>> +		    cdns->dr_mode != USB_DR_MODE_OTG)
>> +			ret = -EINVAL;
>> +	}
>> +
>> +	if (ret) {
>> +		dev_err(cdns->dev, "Incorrect DRD configuration\n");
>> +		return ret;
>> +	}
>> +
>> +	//Updating DR mode according to strap.
>> +	cdns->dr_mode = dr_mode;
>> +	cdns->desired_dr_mode = dr_mode;
>> +	cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
>> +
>> +	dev_info(cdns->dev, "Controller Device ID: %08lx, Revision ID: %08lx\n",
>> +		 CDNS_RID(readl(&cdns->otg_regs->rid)),
>> +		 CDNS_DID(readl(&cdns->otg_regs->did)));
>
>dev_info should be moved at the end if cdns3_drd_update_mode() if it succeeded.
Ok, 
>
>> +
>> +	state = readl(&cdns->otg_regs->sts);
>> +	if (OTGSTS_OTG_NRDY(state) != 0) {
>> +		dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	ret = cdns3_drd_update_mode(cdns);
>> +
>> +	return ret;
>> +}
>> diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
>> new file mode 100644
>> index 000000000000..0faa7520ecac
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/drd.h
>> @@ -0,0 +1,122 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Cadence USB3 DRD part of USBSS driver
>> + *
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Author: Pawel Laszczak <pawell@cadence.com>
>> + */
>> +#ifndef __LINUX_CDNS3_DRD
>> +#define __LINUX_CDNS3_DRD
>> +
>> +#include <linux/usb/otg.h>
>> +#include <linux/phy/phy.h>
>> +#include "core.h"
>> +
>> +/*  DRD register interface. */
>> +struct cdns3_otg_regs {
>> +	__le32 did;
>> +	__le32 rid;
>> +	__le32 capabilities;
>> +	__le32 reserved1;
>> +	__le32 cmd;
>> +	__le32 sts;
>> +	__le32 state;
>> +	__le32 reserved2;
>> +	__le32 ien;
>> +	__le32 ivect;
>> +	__le32 refclk;
>> +	__le32 tmr;
>> +	__le32 reserved3[4];
>> +	__le32 simulate;
>> +	__le32 override;
>> +	__le32 susp_ctrl;
>> +	__le32 reserved4;
>> +	__le32 anasts;
>> +	__le32 adp_ramp_time;
>> +	__le32 ctrl1;
>> +	__le32 ctrl2;
>> +};
>> +
>> +/* CDNS_RID - bitmasks */
>> +#define CDNS_RID(p)			((p) & GENMASK(15, 0))
>> +
>> +/* CDNS_VID - bitmasks */
>> +#define CDNS_DID(p)			((p) & GENMASK(31, 0))
>> +
>> +/* OTGCMD - bitmasks */
>> +/* "Request the bus for Device mode. */
>> +#define OTGCMD_DEV_BUS_REQ	BIT(0)
>> +/* Request the bus for Host mode */
>> +#define OTGCMD_HOST_BUS_REQ		BIT(1)
>> +/* Enable OTG mode. */
>> +#define OTGCMD_OTG_EN			BIT(2)
>> +/* Disable OTG mode */
>> +#define OTGCMD_OTG_DIS			BIT(3)
>> +/*"Configure OTG as A-Device. */
>> +#define OTGCMD_A_DEV_EN			BIT(4)
>> +/*"Configure OTG as A-Device. */
>> +#define OTGCMD_A_DEV_DIS		BIT(5)
>> +/* Drop the bus for Device mod	e. */
>> +#define OTGCMD_DEV_BUS_DROP		BIT(8)
>> +/* Drop the bus for Host mode*/
>> +#define OTGCMD_HOST_BUS_DROP		BIT(9)
>> +/* Power Down USBSS-DEV. */
>> +#define OTGCMD_DEV_POWER_OFF		BIT(11)
>> +/* Power Down CDNSXHCI. */
>> +#define OTGCMD_HOST_POWER_OFF		BIT(12)
>> +
>> +/* OTGIEN - bitmasks */
>> +/* ID change interrupt enable */
>> +#define OTGIEN_ID_CHANGE_INT		BIT(0)
>> +/* Vbusvalid fall detected interrupt enable.*/
>> +#define OTGIEN_VBUSVALID_RISE_INT	BIT(4)
>> +/* Vbusvalid fall detected interrupt enable */
>> +#define OTGIEN_VBUSVALID_FALL_INT	BIT(5)
>> +
>> +/* OTGSTS - bitmasks */
>> +/*
>> + * Current value of the ID pin. It is only valid when idpullup in
>> + *  OTGCTRL1_TYPE register is set to '1'.
>> + */
>> +#define OTGSTS_ID_VALUE			BIT(0)
>> +/* Current value of the vbus_valid */
>> +#define OTGSTS_VBUS_VALID		BIT(1)
>> +/* Current value of the b_sess_vld */
>> +#define OTGSTS_SESSION_VALID		BIT(2)
>> +/*Device mode is active*/
>> +#define OTGSTS_DEV_ACTIVE		BIT(3)
>> +/* Host mode is active. */
>> +#define OTGSTS_HOST_ACTIVE		BIT(4)
>> +/* OTG Controller not ready. */
>> +#define OTGSTS_OTG_NRDY_MASK		BIT(11)
>> +#define OTGSTS_OTG_NRDY(p)		((p) & OTGSTS_OTG_NRDY_MASK)
>> +/*
>> + * Value of the strap pins.
>> + * 000 - no default configuration
>> + * 010 - Controller initiall configured as Host
>> + * 100 - Controller initially configured as Device
>> + */
>> +#define OTGSTS_STRAP(p)			(((p) & GENMASK(14, 12)) >> 12)
>> +#define OTGSTS_STRAP_NO_DEFAULT_CFG	0x00
>> +#define OTGSTS_STRAP_HOST_OTG		0x01
>> +#define OTGSTS_STRAP_HOST		0x02
>> +#define OTGSTS_STRAP_GADGET		0x04
>> +/* Host mode is turned on. */
>> +#define OTGSTSE_XHCI_READYF		BIT(26)
>> +/* "Device mode is turned on .*/
>> +#define OTGSTS_DEV_READY		BIT(27)
>> +
>> +/* OTGREFCLK - bitmasks */
>> +#define OTGREFCLK_STB_CLK_SWITCH_EN	BIT(31)
>> +
>> +/* OTGCTRL1 - bitmasks */
>> +#define OTGCTRL1_IDPULLUP		BIT(24)
>> +
>> +int cdns3_is_host(struct cdns3 *cdns);
>> +int cdns3_is_device(struct cdns3 *cdns);
>> +int cdns3_drd_init(struct cdns3 *cdns);
>> +int cdns3_drd_update_mode(struct cdns3 *cdns);
>> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns);
>> +
>> +#endif /* __LINUX_CDNS3_DRD */
>>
>
>cheers,
>-roger
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

Thanks for all your comments.
Cheers 
Pawel

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

* Re: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-26  7:23     ` Pawel Laszczak
@ 2018-11-26  8:07       ` Roger Quadros
  2018-11-26  8:39         ` Pawel Laszczak
  2018-11-27 11:29       ` Pawel Laszczak
  1 sibling, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-26  8:07 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Pawel,

On 26/11/18 09:23, Pawel Laszczak wrote:
> Hi Roger,
> 
>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>> Patch adds supports for detecting Host/Device mode.
>>> Controller has additional OTG register that allow
>>> implement even whole OTG functionality.
>>> At this moment patch adds support only for detecting
>>> the appropriate mode based on strap pins and ID pin.
>>>
>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>> ---
>>>  drivers/usb/cdns3/Makefile |   2 +-
>>>  drivers/usb/cdns3/core.c   |  27 +++--
>>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>>  create mode 100644 drivers/usb/cdns3/drd.c
>>>  create mode 100644 drivers/usb/cdns3/drd.h
>>>
>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>> index 02d25b23c5d3..e779b2a2f8eb 100644
>>> --- a/drivers/usb/cdns3/Makefile
>>> +++ b/drivers/usb/cdns3/Makefile
>>> @@ -1,5 +1,5 @@
>>>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>
>>> -cdns3-y					:= core.o
>>> +cdns3-y					:= core.o drd.o
>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>> index f9055d4da67f..dbee4325da7f 100644
>>> --- a/drivers/usb/cdns3/core.c
>>> +++ b/drivers/usb/cdns3/core.c
>>> @@ -17,6 +17,7 @@
>>>
>>>  #include "gadget.h"
>>>  #include "core.h"
>>> +#include "drd.h"
>>>
>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>  {
>>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>>  {
>>>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>> -		//TODO: implements selecting device/host mode
>>> -		return CDNS3_ROLE_HOST;
>>> +		if (cdns3_is_host(cdns))
>>> +			return CDNS3_ROLE_HOST;
>>> +		if (cdns3_is_device(cdns))
>>> +			return CDNS3_ROLE_GADGET;
>>>  	}
>>>  	return cdns->roles[CDNS3_ROLE_HOST]
>>>  		? CDNS3_ROLE_HOST
>>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>  	struct cdns3 *cdns = data;
>>>  	irqreturn_t ret = IRQ_NONE;
>>>
>>> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
>>> +		ret = cdns3_drd_irq(cdns);
>>> +		if (ret == IRQ_HANDLED)
>>> +			return ret;
>>> +	}
>>
>> The kernel's shared IRQ model takes care of sharing the same interrupt
>> between different devices and their drivers. You don't need to manually
>> handle it here. Just let all 3 drivers do a request_irq() and have
>> handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
>> IRQ_NONE accordingly.
>>
>> Looks like you can do away with irq member of the role driver struct.
> 
> Ok, I will split it into 3 separate part, but in this case, I additionally have to check the current 
> role in ISR function. Driver can't read host side registers when controller works in device role 
> and vice versa. One part of controller is kept in reset. Only DRD registers are common and are all accessible.
> 

In which ISR do you need to check current role?

I'm not sure if we are on the same page.
Core (drd) driver shouldn't read host/device side registers. All 3 drivers,
i.e. DRD(core), Host (xhci) and device (cdns3) should do a request_irq()
and process their respective IRQ events.

>>> +
>>>  	/* Handle device/host interrupt */
>>>  	if (cdns->role != CDNS3_ROLE_END)
>>>  		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>>> @@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
>>>
>>>  	cdns = container_of(work, struct cdns3, role_switch_wq);
>>>
>>> -	//TODO: implements this functions.
>>> -	//host = cdns3_is_host(cdns);
>>> -	//device = cdns3_is_device(cdns);
>>> -	host = 1;
>>> -	device = 0;
>>> +	host = cdns3_is_host(cdns);
>>> +	device = cdns3_is_device(cdns);
>>
>> What if there is a ID transition between the 2 functions so that
>> and both host and device become true?
>> Since you are checking the ID level separately in both the functions.
>>
>> How about instead having cdns3_get_id() and using
>> it to start/stop relevant roles if we are in OTG mode.
>>
>> Is this going to be used for a role switch even if we're not in OTG mode?
>> If not then it is a BUG if we get here.
>>
> Good point.
> User can change current mode by debugfs and then this function will also invoked.
> Probably I use  cdns3_get_id as you suggest. 
> 
>>>
>>>  	if (host)
>>>  		role = CDNS3_ROLE_HOST;
>>> @@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
>>>  	pm_runtime_get_sync(cdns->dev);
>>>  	cdns3_role_stop(cdns);
>>>
>>> +	if (cdns->desired_dr_mode != cdns->current_dr_mode) {
>>
>> This is about roles, why are we checking dr_mode here?
> 
> Because after changing dr_mode by means of debugfs we need to update mode. 
> Driver should do this after stopping the previous role.  I will move this condition 
> to cdns3_drd_update_mode and add comment in this place. 
> 
>>
>>> +		cdns3_drd_update_mode(cdns);
>>> +		host = cdns3_is_host(cdns);
>>> +		device = cdns3_is_device(cdns);
>>> +	}
>>> +
>>>  	if (host) {
>>>  		if (cdns->roles[CDNS3_ROLE_HOST])
>>>  			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>>> @@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
>>>  	if (ret)
>>>  		goto err2;
>>>
>>> +	ret = cdns3_drd_init(cdns);
>>>  	if (ret)
>>>  		goto err2;
>>>
>>> diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
>>> new file mode 100644
>>> index 000000000000..ac741c80e776
>>> --- /dev/null
>>> +++ b/drivers/usb/cdns3/drd.c
>>> @@ -0,0 +1,229 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Cadence USBSS DRD Driver.
>>> + *
>>> + * Copyright (C) 2018 Cadence.
>>> + *
>>> + * Author: Pawel Laszczak <pawell@cadence.com
>>> + *
>>> + */
>>> +#include <linux/kernel.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/usb/otg.h>
>>> +
>>> +#include "gadget.h"
>>> +#include "drd.h"
>>> +
>>> +/**
>>> + * cdns3_set_mode - change mode of OTG Core
>>> + * @cdns: pointer to context structure
>>> + * @mode: selected mode from cdns_role
>>> + */
>>> +void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
>>> +{
>>> +	u32 reg;
>>> +
>>> +	cdns->current_dr_mode = mode;
>>> +	switch (mode) {
>>> +	case USB_DR_MODE_PERIPHERAL:
>>> +		dev_info(cdns->dev, "Set controller to Gadget mode\n");
>>> +		writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
>>> +		       &cdns->otg_regs->cmd);
>>> +		break;
>>> +	case USB_DR_MODE_HOST:
>>> +		dev_info(cdns->dev, "Set controller to Host mode\n");
>>> +		writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
>>> +		       &cdns->otg_regs->cmd);
>>> +		break;
>>> +	case USB_DR_MODE_OTG:
>>> +		dev_info(cdns->dev, "Set controller to OTG mode\n");
>>> +		reg = readl(&cdns->otg_regs->ctrl1);
>>> +		reg |= OTGCTRL1_IDPULLUP;
>>> +		writel(reg, &cdns->otg_regs->ctrl1);
>>> +
>>> +		/* wait until valid ID (ID_VALUE) can be sampled (50ms). */
>>> +		mdelay(50);
>>> +		break;
>>> +	default:
>>> +		cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
>>> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
>>> +		return;
>>> +	}
>>> +}
>>> +
>>> +static int cdns3_otg_get_id(struct cdns3 *cdns)
>>> +{
>>> +	int id;
>>> +
>>> +	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
>>> +	dev_dbg(cdns->dev, "OTG ID: %d", id);
>>> +	return id;
>>> +}
>>> +
>>> +int cdns3_is_host(struct cdns3 *cdns)
>>> +{
>>> +	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
>>> +		return 1;
>>
>> Why do you need this?
> 
> I assumed that some SoC could have cut DRD /OTG and Device or Host part. 
> In such case the driver cannot be based on ID pin. 
> For only HOST it's not a problem because 
> the standard XHCI driver will be used.  Probably I will remove this fragment.
>>
>>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>>> +		if (!cdns3_otg_get_id(cdns))
>>> +			return 1;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +int cdns3_is_device(struct cdns3 *cdns)
>>> +{
>>> +	if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
>>> +		return 1;
>>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>>> +		if (cdns3_otg_get_id(cdns))
>>> +			return 1;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +/**
>>> + * cdns3_otg_disable_irq - Disable all OTG interrupts
>>> + * @cdns: Pointer to controller context structure
>>> + */
>>> +static void cdns3_otg_disable_irq(struct cdns3 *cdns)
>>> +{
>>> +	writel(0, &cdns->otg_regs->ien);
>>> +}
>>> +
>>> +/**
>>> + * cdns3_otg_enable_irq - enable id and sess_valid interrupts
>>> + * @cdns: Pointer to controller context structure
>>> + */
>>> +static void cdns3_otg_enable_irq(struct cdns3 *cdns)
>>> +{
>>> +	writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
>>> +	       OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
>>> +}
>>> +
>>> +/**
>>> + * cdns3_init_otg_mode - initialize drd controller
>>> + * @cdns: Pointer to controller context structure
>>> + *
>>> + * Returns 0 on success otherwise negative errno
>>> + */
>>> +static void cdns3_init_otg_mode(struct cdns3 *cdns)
>>> +{
>>> +	cdns3_otg_disable_irq(cdns);
>>> +	/* clear all interrupts */
>>> +	writel(~0, &cdns->otg_regs->ivect);
>>> +
>>> +	cdns3_set_mode(cdns, USB_DR_MODE_OTG);
>>> +
>>> +	cdns3_otg_enable_irq(cdns);
>>> +}
>>> +
>>> +/**
>>> + * cdns3_drd_update_mode - initialize mode of operation
>>
>> Looks like this will be called only once. How about calling it
>>
>> cdns3_drd_init_mode()?
> 
> It will be also called after changing dr_mode from debugfs.
> 
>>> + * @cdns: Pointer to controller context structure
>>> + *
>>> + * Returns 0 on success otherwise negative errno
>>> + */
>>> +int cdns3_drd_update_mode(struct cdns3 *cdns)
>>> +{
>>> +	int ret = 0;
>>> +
>>> +	switch (cdns->desired_dr_mode) {
>>
>> I think we can get rid of desired_dr_mode member in struct cdns.
>> Just pass the mode as an argument to cdns3_drd_init_mode()
> 
> This will be used also in patch that introduce debugfs. This filed is also used 
> during changing dr_mode from user space.
> 
> My intention was:
> dr_mode - indicated what driver can support, this filed based on dr_mode from device tree, 
> 	     straps bits from otg register and kernel configuration. 
> desired_ dr_mode - the next mode desired by user, changed by debugfs 
> current_dr_mode  - actually selected mode 
> 

OK, makes sense. But let's keep this patch simple. Add only the members you need right now.
Introduce the new one (desired_dr_mode) in the debugfs patch.

>>
>> And we already have cdns->dr_mode.
>>
>>> +	case USB_DR_MODE_PERIPHERAL:
>>> +		cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
>>> +		break;
>>> +	case USB_DR_MODE_HOST:
>>> +		cdns3_set_mode(cdns, USB_DR_MODE_HOST);
>>> +		break;
>>> +	case USB_DR_MODE_OTG:
>>> +		cdns3_init_otg_mode(cdns);
>>> +		break;
>>> +	default:
>>> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n",
>>> +			cdns->dr_mode);
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	return ret;
>>> +}

<snip>

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 06/15] usb:cdns3: Adds Host support
  2018-11-23 14:23   ` Roger Quadros
@ 2018-11-26  8:24     ` Pawel Laszczak
  2018-11-26  9:50       ` Roger Quadros
  2018-12-05  8:41     ` Peter Chen
  1 sibling, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-26  8:24 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

>EXTERNAL MAIL
>
>
>On 18/11/18 12:09, Pawel Laszczak wrote:
>> Patch adds host-export.h and host.c file and mplements functions that
>> allow to initialize, start and stop XHCI host driver.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/Kconfig       |  10 ++
>>  drivers/usb/cdns3/Makefile      |   1 +
>>  drivers/usb/cdns3/core.c        |   7 +-
>>  drivers/usb/cdns3/host-export.h |  30 ++++
>>  drivers/usb/cdns3/host.c        | 256 ++++++++++++++++++++++++++++++++
>>  5 files changed, 302 insertions(+), 2 deletions(-)
>>  create mode 100644 drivers/usb/cdns3/host-export.h
>>  create mode 100644 drivers/usb/cdns3/host.c
>>
>> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
>> index eb22a8692991..d92bc3d68eb0 100644
>> --- a/drivers/usb/cdns3/Kconfig
>> +++ b/drivers/usb/cdns3/Kconfig
>> @@ -10,6 +10,16 @@ config USB_CDNS3
>>
>>  if USB_CDNS3
>>
>> +config USB_CDNS3_HOST
>> +        bool "Cadence USB3 host controller"
>> +        depends on USB_XHCI_HCD
>> +        help
>> +          Say Y here to enable host controller functionality of the
>> +          cadence driver.
>> +
>> +          Host controller is compliance with XHCI so it will use
>> +          standard XHCI driver.
>> +
>>  config USB_CDNS3_PCI_WRAP
>>  	tristate "PCIe-based Platforms"
>>  	depends on USB_PCI && ACPI
>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> index e779b2a2f8eb..976117ba67ff 100644
>> --- a/drivers/usb/cdns3/Makefile
>> +++ b/drivers/usb/cdns3/Makefile
>> @@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>
>>  cdns3-y					:= core.o drd.o
>> +cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> index dbee4325da7f..4cb820be9ff3 100644
>> --- a/drivers/usb/cdns3/core.c
>> +++ b/drivers/usb/cdns3/core.c
>> @@ -17,6 +17,7 @@
>>
>>  #include "gadget.h"
>>  #include "core.h"
>> +#include "host-export.h"
>>  #include "drd.h"
>>
>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>> @@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
>>  	}
>>
>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>> -		//TODO: implements host initialization
>> +		if (cdns3_host_init(cdns))
>> +			dev_info(dev, "doesn't support host\n");
>
>dev_err()
>
>And you need to error out with error code.

ok, but I assume that even if host returns error then we can use 
only device role. Only when both functions return errors, then it's  a critical error 
and function return error code. 
>
>>  	}
>>
>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>> @@ -142,7 +144,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>
>>  static void cdns3_remove_roles(struct cdns3 *cdns)
>>  {
>> -	//TODO: implements this function
>
>if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST)
>
>> +	cdns3_host_remove(cdns);
>
>How about calling it cdns3_host_exit() to complement cdns3_host_init().
>
>>  }
>>
>>  static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>> @@ -410,6 +412,7 @@ static struct platform_driver cdns3_driver = {
>>
>>  static int __init cdns3_driver_platform_register(void)
>>  {
>> +	cdns3_host_driver_init();
>>  	return platform_driver_register(&cdns3_driver);
>>  }
>>  module_init(cdns3_driver_platform_register);
>> diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
>> new file mode 100644
>> index 000000000000..f8f3b230b472
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/host-export.h
>> @@ -0,0 +1,30 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Cadence USBSS DRD Driver -Host Export APIs
>> + *
>> + * Copyright (C) 2017 NXP
>> + *
>> + * Authors: Peter Chen <peter.chen@nxp.com>
>> + */
>> +#ifndef __LINUX_CDNS3_HOST_EXPORT
>> +#define __LINUX_CDNS3_HOST_EXPORT
>> +
>> +#ifdef CONFIG_USB_CDNS3_HOST
>> +
>> +int cdns3_host_init(struct cdns3 *cdns);
>> +void cdns3_host_remove(struct cdns3 *cdns);
>> +void cdns3_host_driver_init(void);
>> +
>> +#else
>> +
>> +static inline int cdns3_host_init(struct cdns3 *cdns)
>> +{
>> +	return -ENXIO;
>> +}
>> +
>> +static inline void cdns3_host_remove(struct cdns3 *cdns) { }
>> +static inline void cdns3_host_driver_init(void) {}
>> +
>> +#endif /* CONFIG_USB_CDNS3_HOST */
>> +
>> +#endif /* __LINUX_CDNS3_HOST_EXPORT */
>> diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
>> new file mode 100644
>> index 000000000000..0dd47976cb28
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/host.c
>> @@ -0,0 +1,256 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver - host side
>> + *
>> + * Copyright (C) 2018 Cadence Design Systems.
>> + * Copyright (C) 2018 NXP
>> + *
>> + * Authors: Peter Chen <peter.chen@nxp.com>
>> + *	    Pawel Laszczak <pawell@cadence.com>
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/device.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/usb.h>
>> +#include <linux/usb/hcd.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/usb/of.h>
>> +
>> +#include "../host/xhci.h"
>> +#include "core.h"
>> +#include "host-export.h"
>> +
>> +static struct hc_driver __read_mostly xhci_cdns3_hc_driver;
>> +
>> +static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci)
>> +{
>> +	/*
>> +	 * As of now platform drivers don't provide MSI support so we ensure
>> +	 * here that the generic code does not try to make a pci_dev from our
>> +	 * dev struct in order to setup MSI
>> +	 */
>> +	xhci->quirks |= XHCI_PLAT;
>> +}
>> +
>> +static int xhci_cdns3_setup(struct usb_hcd *hcd)
>> +{
>> +	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
>> +	u32 command;
>> +	int ret;
>> +
>> +	ret = xhci_gen_setup(hcd, xhci_cdns3_quirks);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* set usbcmd.EU3S */
>> +	command = readl(&xhci->op_regs->command);
>> +	command |= CMD_PM_INDEX;
>> +	writel(command, &xhci->op_regs->command);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
>> +	.extra_priv_size = sizeof(struct xhci_hcd),
>> +	.reset = xhci_cdns3_setup,
>> +};
>> +
>> +struct cdns3_host {
>> +	struct device dev;
>> +	struct usb_hcd *hcd;
>> +};
>> +
>> +static irqreturn_t cdns3_host_irq(struct cdns3 *cdns)
>> +{
>> +	struct device *dev = cdns->host_dev;
>> +	struct usb_hcd	*hcd;
>> +
>> +	if (dev)
>> +		hcd = dev_get_drvdata(dev);
>> +	else
>> +		return IRQ_NONE;
>> +
>> +	if (hcd)
>> +		return usb_hcd_irq(cdns->irq, hcd);
>> +	else
>> +		return IRQ_NONE;
>
>Why can't you just reuse the xhci-platform driver and let it manage the IRQ?
>Since it is a shared IRQ, different drivers can request the same IRQ and return IRQ_NONE
>if the IRQ wasn't from their device.

In device role the host part of controller is kept in reset, so driver can't read the host register. 
Such solution allows driver to control access to host register. 
So if driver has shared separate interrupt for host role then it has to check if controller work in 
Host role.  

>> +}
>> +
>> +static void cdns3_host_release(struct device *dev)
>> +{
>> +	struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
>> +
>> +	kfree(host);
>> +}
>> +
>> +static int cdns3_host_start(struct cdns3 *cdns)
>> +{
>> +	struct cdns3_host *host;
>> +	struct device *dev;
>> +	struct device *sysdev;
>> +	struct xhci_hcd	*xhci;
>> +	int ret;
>> +
>> +	host = kzalloc(sizeof(*host), GFP_KERNEL);
>> +	if (!host)
>> +		return -ENOMEM;
>> +
>> +	dev = &host->dev;
>> +	dev->release = cdns3_host_release;
>> +	dev->parent = cdns->dev;
>> +	dev_set_name(dev, "xhci-cdns3");
>> +	cdns->host_dev = dev;
>> +	ret = device_register(dev);
>> +	if (ret)
>> +		goto err1;
>> +
>> +	sysdev = cdns->dev;
>> +	/* Try to set 64-bit DMA first */
>> +	if (WARN_ON(!sysdev->dma_mask))
>> +		/* Platform did not initialize dma_mask */
>> +		ret = dma_coerce_mask_and_coherent(sysdev,
>> +						   DMA_BIT_MASK(64));
>> +	else
>> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
>> +
>> +	/* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
>> +	if (ret) {
>> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	pm_runtime_set_active(dev);
>> +	pm_runtime_no_callbacks(dev);
>> +	pm_runtime_enable(dev);
>> +	host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
>> +				     dev_name(dev), NULL);
>> +	if (!host->hcd) {
>> +		ret = -ENOMEM;
>> +		goto err2;
>> +	}
>> +
>> +	host->hcd->regs = cdns->xhci_regs;
>> +	host->hcd->rsrc_start = cdns->xhci_res->start;
>> +	host->hcd->rsrc_len = resource_size(cdns->xhci_res);
>> +
>> +	device_wakeup_enable(host->hcd->self.controller);
>> +	xhci = hcd_to_xhci(host->hcd);
>> +
>> +	xhci->main_hcd = host->hcd;
>> +	xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
>> +					    dev_name(dev), host->hcd);
>> +	if (!xhci->shared_hcd) {
>> +		ret = -ENOMEM;
>> +		goto err3;
>> +	}
>> +
>> +	host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
>> +	xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
>> +	ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
>> +	if (ret)
>> +		goto err4;
>> +
>> +	ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
>> +	if (ret)
>> +		goto err5;
>> +
>> +	device_set_wakeup_capable(dev, true);
>
>All this is being done by the xhci-plat.c
>
>You can make use of it by just creating a xhci-hcd platform device.
>
>e.g.
>platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
>platform_device_add_resources() to add IRQ and memory resource.
>platform_device_add_properties() to add any quirks.
>platform_device_add()
>

If we do this in this way driver will not control the interrupt. 
This code  has written by Peter Chan and I am convinced 
that this concept is only correct one.

>> +
>> +	return 0;
>> +
>> +err5:
>> +	usb_remove_hcd(host->hcd);
>> +err4:
>> +	usb_put_hcd(xhci->shared_hcd);
>> +err3:
>> +	usb_put_hcd(host->hcd);
>> +err2:
>> +	device_del(dev);
>> +err1:
>> +	put_device(dev);
>> +	cdns->host_dev = NULL;
>> +	return ret;
>> +}
>> +
>> +static void cdns3_host_stop(struct cdns3 *cdns)
>> +{
>> +	struct device *dev = cdns->host_dev;
>> +	struct xhci_hcd	*xhci;
>> +	struct usb_hcd	*hcd;
>> +
>> +	if (dev) {
>> +		hcd = dev_get_drvdata(dev);
>> +		xhci = hcd_to_xhci(hcd);
>> +		usb_remove_hcd(xhci->shared_hcd);
>> +		usb_remove_hcd(hcd);
>> +		synchronize_irq(cdns->irq);
>> +		usb_put_hcd(xhci->shared_hcd);
>> +		usb_put_hcd(hcd);
>> +		cdns->host_dev = NULL;
>> +		pm_runtime_set_suspended(dev);
>> +		pm_runtime_disable(dev);
>> +		device_del(dev);
>> +		put_device(dev);
>> +	}
>
>You can replace this with just
>	platform_device_unregister(xhci_dev);
>
>> +}
>> +
>> +#if CONFIG_PM
>> +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
>> +{
>> +	struct device *dev = cdns->host_dev;
>> +	struct xhci_hcd	*xhci;
>> +
>> +	if (!dev)
>> +		return 0;
>> +
>> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
>> +	return xhci_suspend(xhci, do_wakeup);
>> +}
>> +
>> +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
>> +{
>> +	struct device *dev = cdns->host_dev;
>> +	struct xhci_hcd	*xhci;
>> +
>> +	if (!dev)
>> +		return 0;
>> +
>> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
>> +	return xhci_resume(xhci, hibernated);
>> +}
>
>These won't be required any more as xhci-plat is doing this.
>
>> +#endif /* CONFIG_PM */
>> +
>> +int cdns3_host_init(struct cdns3 *cdns)
>> +{
>> +	struct cdns3_role_driver *rdrv;
>> +
>> +	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
>> +	if (!rdrv)
>> +		return -ENOMEM;
>> +
>> +	rdrv->start	= cdns3_host_start;
>> +	rdrv->stop	= cdns3_host_stop;
>> +	rdrv->irq	= cdns3_host_irq;
>> +#if CONFIG_PM
>> +	rdrv->suspend	= cdns3_host_suspend;
>> +	rdrv->resume	= cdns3_host_resume;
>> +#endif /* CONFIG_PM */
>> +	rdrv->name	= "host";
>> +	cdns->roles[CDNS3_ROLE_HOST] = rdrv;
>> +
>> +	return 0;
>> +}
>> +
>> +void cdns3_host_remove(struct cdns3 *cdns)
>> +{
>> +	cdns3_host_stop(cdns);
>
>calling cdns3_host_stop() here can lead to problems as Controller might be in
>peripheral mode at this point. The core driver needs to ensure that relevant role
>is stopped before calling cdns3_host_remove().
>
>Here you need to unregister the role driver though.
>
>cdns->roles[CDNS3_ROLE_HOST] = NULL;
>
This function can be called only in host mode/role. It operate on host registers. 
This checking is provided in core.c file. 
>> +}
>> +
>> +void __init cdns3_host_driver_init(void)
>> +{
>> +	xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
>> +}
>>
>
>cheers,
>-roger
>
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-26  8:07       ` Roger Quadros
@ 2018-11-26  8:39         ` Pawel Laszczak
  2018-11-26  9:39           ` Roger Quadros
  0 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-26  8:39 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

>
>Pawel,
>
>On 26/11/18 09:23, Pawel Laszczak wrote:
>> Hi Roger,
>>
>>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>>> Patch adds supports for detecting Host/Device mode.
>>>> Controller has additional OTG register that allow
>>>> implement even whole OTG functionality.
>>>> At this moment patch adds support only for detecting
>>>> the appropriate mode based on strap pins and ID pin.
>>>>
>>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>>> ---
>>>>  drivers/usb/cdns3/Makefile |   2 +-
>>>>  drivers/usb/cdns3/core.c   |  27 +++--
>>>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>>>  create mode 100644 drivers/usb/cdns3/drd.c
>>>>  create mode 100644 drivers/usb/cdns3/drd.h
>>>>
>>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>>> index 02d25b23c5d3..e779b2a2f8eb 100644
>>>> --- a/drivers/usb/cdns3/Makefile
>>>> +++ b/drivers/usb/cdns3/Makefile
>>>> @@ -1,5 +1,5 @@
>>>>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>>
>>>> -cdns3-y					:= core.o
>>>> +cdns3-y					:= core.o drd.o
>>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>>> index f9055d4da67f..dbee4325da7f 100644
>>>> --- a/drivers/usb/cdns3/core.c
>>>> +++ b/drivers/usb/cdns3/core.c
>>>> @@ -17,6 +17,7 @@
>>>>
>>>>  #include "gadget.h"
>>>>  #include "core.h"
>>>> +#include "drd.h"
>>>>
>>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>>  {
>>>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>>>  {
>>>>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>>> -		//TODO: implements selecting device/host mode
>>>> -		return CDNS3_ROLE_HOST;
>>>> +		if (cdns3_is_host(cdns))
>>>> +			return CDNS3_ROLE_HOST;
>>>> +		if (cdns3_is_device(cdns))
>>>> +			return CDNS3_ROLE_GADGET;
>>>>  	}
>>>>  	return cdns->roles[CDNS3_ROLE_HOST]
>>>>  		? CDNS3_ROLE_HOST
>>>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>>  	struct cdns3 *cdns = data;
>>>>  	irqreturn_t ret = IRQ_NONE;
>>>>
>>>> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
>>>> +		ret = cdns3_drd_irq(cdns);
>>>> +		if (ret == IRQ_HANDLED)
>>>> +			return ret;
>>>> +	}
>>>
>>> The kernel's shared IRQ model takes care of sharing the same interrupt
>>> between different devices and their drivers. You don't need to manually
>>> handle it here. Just let all 3 drivers do a request_irq() and have
>>> handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
>>> IRQ_NONE accordingly.
>>>
>>> Looks like you can do away with irq member of the role driver struct.
>>
>> Ok, I will split it into 3 separate part, but in this case, I additionally have to check the current
>> role in ISR function. Driver can't read host side registers when controller works in device role
>> and vice versa. One part of controller is kept in reset. Only DRD registers are common and are all accessible.
>>
>
>In which ISR do you need to check current role?
>
>I'm not sure if we are on the same page.
>Core (drd) driver shouldn't read host/device side registers. All 3 drivers,
>i.e. DRD(core), Host (xhci) and device (cdns3) should do a request_irq()
>and process their respective IRQ events.

Yes, I understand. 
I need to check this in cdns3_irq_handler_thread and cdns3_host_irq.

Core (drd) has register that are always accessible. 
Core (device)  - registers are available also in device mode
Core (host)  - registers  are available only in host mode 

So we can use separate request_irq  for drd, device and host side, but 
we need ensure that in host side driver will not touch the device register. 

We doesn't know the order in which  the system will call interrupts function related to 
 the shared interrupt line.

>
>>>> +
>>>>  	/* Handle device/host interrupt */
>>>>  	if (cdns->role != CDNS3_ROLE_END)
>>>>  		ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>>>> @@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
>>>>
>>>>  	cdns = container_of(work, struct cdns3, role_switch_wq);
>>>>
>>>> -	//TODO: implements this functions.
>>>> -	//host = cdns3_is_host(cdns);
>>>> -	//device = cdns3_is_device(cdns);
>>>> -	host = 1;
>>>> -	device = 0;
>>>> +	host = cdns3_is_host(cdns);
>>>> +	device = cdns3_is_device(cdns);
>>>
>>> What if there is a ID transition between the 2 functions so that
>>> and both host and device become true?
>>> Since you are checking the ID level separately in both the functions.
>>>
>>> How about instead having cdns3_get_id() and using
>>> it to start/stop relevant roles if we are in OTG mode.
>>>
>>> Is this going to be used for a role switch even if we're not in OTG mode?
>>> If not then it is a BUG if we get here.
>>>
>> Good point.
>> User can change current mode by debugfs and then this function will also invoked.
>> Probably I use  cdns3_get_id as you suggest.
>>
>>>>
>>>>  	if (host)
>>>>  		role = CDNS3_ROLE_HOST;
>>>> @@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
>>>>  	pm_runtime_get_sync(cdns->dev);
>>>>  	cdns3_role_stop(cdns);
>>>>
>>>> +	if (cdns->desired_dr_mode != cdns->current_dr_mode) {
>>>
>>> This is about roles, why are we checking dr_mode here?
>>
>> Because after changing dr_mode by means of debugfs we need to update mode.
>> Driver should do this after stopping the previous role.  I will move this condition
>> to cdns3_drd_update_mode and add comment in this place.
>>
>>>
>>>> +		cdns3_drd_update_mode(cdns);
>>>> +		host = cdns3_is_host(cdns);
>>>> +		device = cdns3_is_device(cdns);
>>>> +	}
>>>> +
>>>>  	if (host) {
>>>>  		if (cdns->roles[CDNS3_ROLE_HOST])
>>>>  			cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>>>> @@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
>>>>  	if (ret)
>>>>  		goto err2;
>>>>
>>>> +	ret = cdns3_drd_init(cdns);
>>>>  	if (ret)
>>>>  		goto err2;
>>>>
>>>> diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
>>>> new file mode 100644
>>>> index 000000000000..ac741c80e776
>>>> --- /dev/null
>>>> +++ b/drivers/usb/cdns3/drd.c
>>>> @@ -0,0 +1,229 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * Cadence USBSS DRD Driver.
>>>> + *
>>>> + * Copyright (C) 2018 Cadence.
>>>> + *
>>>> + * Author: Pawel Laszczak <pawell@cadence.com
>>>> + *
>>>> + */
>>>> +#include <linux/kernel.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/delay.h>
>>>> +#include <linux/usb/otg.h>
>>>> +
>>>> +#include "gadget.h"
>>>> +#include "drd.h"
>>>> +
>>>> +/**
>>>> + * cdns3_set_mode - change mode of OTG Core
>>>> + * @cdns: pointer to context structure
>>>> + * @mode: selected mode from cdns_role
>>>> + */
>>>> +void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
>>>> +{
>>>> +	u32 reg;
>>>> +
>>>> +	cdns->current_dr_mode = mode;
>>>> +	switch (mode) {
>>>> +	case USB_DR_MODE_PERIPHERAL:
>>>> +		dev_info(cdns->dev, "Set controller to Gadget mode\n");
>>>> +		writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
>>>> +		       &cdns->otg_regs->cmd);
>>>> +		break;
>>>> +	case USB_DR_MODE_HOST:
>>>> +		dev_info(cdns->dev, "Set controller to Host mode\n");
>>>> +		writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
>>>> +		       &cdns->otg_regs->cmd);
>>>> +		break;
>>>> +	case USB_DR_MODE_OTG:
>>>> +		dev_info(cdns->dev, "Set controller to OTG mode\n");
>>>> +		reg = readl(&cdns->otg_regs->ctrl1);
>>>> +		reg |= OTGCTRL1_IDPULLUP;
>>>> +		writel(reg, &cdns->otg_regs->ctrl1);
>>>> +
>>>> +		/* wait until valid ID (ID_VALUE) can be sampled (50ms). */
>>>> +		mdelay(50);
>>>> +		break;
>>>> +	default:
>>>> +		cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
>>>> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
>>>> +		return;
>>>> +	}
>>>> +}
>>>> +
>>>> +static int cdns3_otg_get_id(struct cdns3 *cdns)
>>>> +{
>>>> +	int id;
>>>> +
>>>> +	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
>>>> +	dev_dbg(cdns->dev, "OTG ID: %d", id);
>>>> +	return id;
>>>> +}
>>>> +
>>>> +int cdns3_is_host(struct cdns3 *cdns)
>>>> +{
>>>> +	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
>>>> +		return 1;
>>>
>>> Why do you need this?
>>
>> I assumed that some SoC could have cut DRD /OTG and Device or Host part.
>> In such case the driver cannot be based on ID pin.
>> For only HOST it's not a problem because
>> the standard XHCI driver will be used.  Probably I will remove this fragment.
>>>
>>>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>>>> +		if (!cdns3_otg_get_id(cdns))
>>>> +			return 1;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +int cdns3_is_device(struct cdns3 *cdns)
>>>> +{
>>>> +	if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
>>>> +		return 1;
>>>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>>>> +		if (cdns3_otg_get_id(cdns))
>>>> +			return 1;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_otg_disable_irq - Disable all OTG interrupts
>>>> + * @cdns: Pointer to controller context structure
>>>> + */
>>>> +static void cdns3_otg_disable_irq(struct cdns3 *cdns)
>>>> +{
>>>> +	writel(0, &cdns->otg_regs->ien);
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_otg_enable_irq - enable id and sess_valid interrupts
>>>> + * @cdns: Pointer to controller context structure
>>>> + */
>>>> +static void cdns3_otg_enable_irq(struct cdns3 *cdns)
>>>> +{
>>>> +	writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
>>>> +	       OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_init_otg_mode - initialize drd controller
>>>> + * @cdns: Pointer to controller context structure
>>>> + *
>>>> + * Returns 0 on success otherwise negative errno
>>>> + */
>>>> +static void cdns3_init_otg_mode(struct cdns3 *cdns)
>>>> +{
>>>> +	cdns3_otg_disable_irq(cdns);
>>>> +	/* clear all interrupts */
>>>> +	writel(~0, &cdns->otg_regs->ivect);
>>>> +
>>>> +	cdns3_set_mode(cdns, USB_DR_MODE_OTG);
>>>> +
>>>> +	cdns3_otg_enable_irq(cdns);
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_drd_update_mode - initialize mode of operation
>>>
>>> Looks like this will be called only once. How about calling it
>>>
>>> cdns3_drd_init_mode()?
>>
>> It will be also called after changing dr_mode from debugfs.
>>
>>>> + * @cdns: Pointer to controller context structure
>>>> + *
>>>> + * Returns 0 on success otherwise negative errno
>>>> + */
>>>> +int cdns3_drd_update_mode(struct cdns3 *cdns)
>>>> +{
>>>> +	int ret = 0;
>>>> +
>>>> +	switch (cdns->desired_dr_mode) {
>>>
>>> I think we can get rid of desired_dr_mode member in struct cdns.
>>> Just pass the mode as an argument to cdns3_drd_init_mode()
>>
>> This will be used also in patch that introduce debugfs. This filed is also used
>> during changing dr_mode from user space.
>>
>> My intention was:
>> dr_mode - indicated what driver can support, this filed based on dr_mode from device tree,
>> 	     straps bits from otg register and kernel configuration.
>> desired_ dr_mode - the next mode desired by user, changed by debugfs
>> current_dr_mode  - actually selected mode
>>
>
>OK, makes sense. But let's keep this patch simple. Add only the members you need right now.
>Introduce the new one (desired_dr_mode) in the debugfs patch.

Ok,  I will try to reorganize these two patches. 
>
>>>
>>> And we already have cdns->dr_mode.
>>>
>>>> +	case USB_DR_MODE_PERIPHERAL:
>>>> +		cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
>>>> +		break;
>>>> +	case USB_DR_MODE_HOST:
>>>> +		cdns3_set_mode(cdns, USB_DR_MODE_HOST);
>>>> +		break;
>>>> +	case USB_DR_MODE_OTG:
>>>> +		cdns3_init_otg_mode(cdns);
>>>> +		break;
>>>> +	default:
>>>> +		dev_err(cdns->dev, "Unsupported mode of operation %d\n",
>>>> +			cdns->dr_mode);
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>
><snip>
>
>cheers,
>-roger
>
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-26  8:39         ` Pawel Laszczak
@ 2018-11-26  9:39           ` Roger Quadros
  2018-11-26 10:09             ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-26  9:39 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

On 26/11/18 10:39, Pawel Laszczak wrote:
>>
>> Pawel,
>>
>> On 26/11/18 09:23, Pawel Laszczak wrote:
>>> Hi Roger,
>>>
>>>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>>>> Patch adds supports for detecting Host/Device mode.
>>>>> Controller has additional OTG register that allow
>>>>> implement even whole OTG functionality.
>>>>> At this moment patch adds support only for detecting
>>>>> the appropriate mode based on strap pins and ID pin.
>>>>>
>>>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>>>> ---
>>>>>  drivers/usb/cdns3/Makefile |   2 +-
>>>>>  drivers/usb/cdns3/core.c   |  27 +++--
>>>>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>>>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>>>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>>>>  create mode 100644 drivers/usb/cdns3/drd.c
>>>>>  create mode 100644 drivers/usb/cdns3/drd.h
>>>>>
>>>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>>>> index 02d25b23c5d3..e779b2a2f8eb 100644
>>>>> --- a/drivers/usb/cdns3/Makefile
>>>>> +++ b/drivers/usb/cdns3/Makefile
>>>>> @@ -1,5 +1,5 @@
>>>>>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>>>
>>>>> -cdns3-y					:= core.o
>>>>> +cdns3-y					:= core.o drd.o
>>>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>>>> index f9055d4da67f..dbee4325da7f 100644
>>>>> --- a/drivers/usb/cdns3/core.c
>>>>> +++ b/drivers/usb/cdns3/core.c
>>>>> @@ -17,6 +17,7 @@
>>>>>
>>>>>  #include "gadget.h"
>>>>>  #include "core.h"
>>>>> +#include "drd.h"
>>>>>
>>>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>>>  {
>>>>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>>>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>>>>  {
>>>>>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>>>> -		//TODO: implements selecting device/host mode
>>>>> -		return CDNS3_ROLE_HOST;
>>>>> +		if (cdns3_is_host(cdns))
>>>>> +			return CDNS3_ROLE_HOST;
>>>>> +		if (cdns3_is_device(cdns))
>>>>> +			return CDNS3_ROLE_GADGET;
>>>>>  	}
>>>>>  	return cdns->roles[CDNS3_ROLE_HOST]
>>>>>  		? CDNS3_ROLE_HOST
>>>>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>>>  	struct cdns3 *cdns = data;
>>>>>  	irqreturn_t ret = IRQ_NONE;
>>>>>
>>>>> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
>>>>> +		ret = cdns3_drd_irq(cdns);
>>>>> +		if (ret == IRQ_HANDLED)
>>>>> +			return ret;
>>>>> +	}
>>>>
>>>> The kernel's shared IRQ model takes care of sharing the same interrupt
>>>> between different devices and their drivers. You don't need to manually
>>>> handle it here. Just let all 3 drivers do a request_irq() and have
>>>> handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
>>>> IRQ_NONE accordingly.
>>>>
>>>> Looks like you can do away with irq member of the role driver struct.
>>>
>>> Ok, I will split it into 3 separate part, but in this case, I additionally have to check the current
>>> role in ISR function. Driver can't read host side registers when controller works in device role
>>> and vice versa. One part of controller is kept in reset. Only DRD registers are common and are all accessible.
>>>
>>
>> In which ISR do you need to check current role?
>>
>> I'm not sure if we are on the same page.
>> Core (drd) driver shouldn't read host/device side registers. All 3 drivers,
>> i.e. DRD(core), Host (xhci) and device (cdns3) should do a request_irq()
>> and process their respective IRQ events.
> 
> Yes, I understand. 
> I need to check this in cdns3_irq_handler_thread and cdns3_host_irq.
> 
> Core (drd) has register that are always accessible. 
> Core (device)  - registers are available also in device mode
> Core (host)  - registers  are available only in host mode 
> 
> So we can use separate request_irq  for drd, device and host side, but 
> we need ensure that in host side driver will not touch the device register. 

That should never happen as host side doesn't have visibility to device registers.
> 
> We doesn't know the order in which  the system will call interrupts function related to 
>  the shared interrupt line.

Order shouldn't matter. Each user will check its own events return IRQ_NONE if they
didn't cause the IRQ.
> 

<snip>

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 06/15] usb:cdns3: Adds Host support
  2018-11-26  8:24     ` Pawel Laszczak
@ 2018-11-26  9:50       ` Roger Quadros
  2018-11-26 10:17         ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-26  9:50 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi,

On 26/11/18 10:24, Pawel Laszczak wrote:
>> EXTERNAL MAIL
>>
>>
>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>> Patch adds host-export.h and host.c file and mplements functions that
>>> allow to initialize, start and stop XHCI host driver.
>>>
>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>> ---
>>>  drivers/usb/cdns3/Kconfig       |  10 ++
>>>  drivers/usb/cdns3/Makefile      |   1 +
>>>  drivers/usb/cdns3/core.c        |   7 +-
>>>  drivers/usb/cdns3/host-export.h |  30 ++++
>>>  drivers/usb/cdns3/host.c        | 256 ++++++++++++++++++++++++++++++++
>>>  5 files changed, 302 insertions(+), 2 deletions(-)
>>>  create mode 100644 drivers/usb/cdns3/host-export.h
>>>  create mode 100644 drivers/usb/cdns3/host.c
>>>
>>> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
>>> index eb22a8692991..d92bc3d68eb0 100644
>>> --- a/drivers/usb/cdns3/Kconfig
>>> +++ b/drivers/usb/cdns3/Kconfig
>>> @@ -10,6 +10,16 @@ config USB_CDNS3
>>>
>>>  if USB_CDNS3
>>>
>>> +config USB_CDNS3_HOST
>>> +        bool "Cadence USB3 host controller"
>>> +        depends on USB_XHCI_HCD
>>> +        help
>>> +          Say Y here to enable host controller functionality of the
>>> +          cadence driver.
>>> +
>>> +          Host controller is compliance with XHCI so it will use
>>> +          standard XHCI driver.
>>> +
>>>  config USB_CDNS3_PCI_WRAP
>>>  	tristate "PCIe-based Platforms"
>>>  	depends on USB_PCI && ACPI
>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>> index e779b2a2f8eb..976117ba67ff 100644
>>> --- a/drivers/usb/cdns3/Makefile
>>> +++ b/drivers/usb/cdns3/Makefile
>>> @@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>
>>>  cdns3-y					:= core.o drd.o
>>> +cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>> index dbee4325da7f..4cb820be9ff3 100644
>>> --- a/drivers/usb/cdns3/core.c
>>> +++ b/drivers/usb/cdns3/core.c
>>> @@ -17,6 +17,7 @@
>>>
>>>  #include "gadget.h"
>>>  #include "core.h"
>>> +#include "host-export.h"
>>>  #include "drd.h"
>>>
>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>> @@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
>>>  	}
>>>
>>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>>> -		//TODO: implements host initialization
>>> +		if (cdns3_host_init(cdns))
>>> +			dev_info(dev, "doesn't support host\n");
>>
>> dev_err()
>>
>> And you need to error out with error code.
> 
> ok, but I assume that even if host returns error then we can use 
> only device role. Only when both functions return errors, then it's  a critical error 
> and function return error code. 

But at this point we are in OTG or HOST dr_mode and without host functional
both will not function correctly. So we must error out so user can debug.

>>
>>>  	}
>>>
>>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>>> @@ -142,7 +144,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>
>>>  static void cdns3_remove_roles(struct cdns3 *cdns)
>>>  {
>>> -	//TODO: implements this function
>>
>> if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST)
>>
>>> +	cdns3_host_remove(cdns);
>>
>> How about calling it cdns3_host_exit() to complement cdns3_host_init().
>>
>>>  }
>>>
>>>  static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>>> @@ -410,6 +412,7 @@ static struct platform_driver cdns3_driver = {
>>>
>>>  static int __init cdns3_driver_platform_register(void)
>>>  {
>>> +	cdns3_host_driver_init();
>>>  	return platform_driver_register(&cdns3_driver);
>>>  }
>>>  module_init(cdns3_driver_platform_register);
>>> diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
>>> new file mode 100644
>>> index 000000000000..f8f3b230b472
>>> --- /dev/null
>>> +++ b/drivers/usb/cdns3/host-export.h
>>> @@ -0,0 +1,30 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * Cadence USBSS DRD Driver -Host Export APIs
>>> + *
>>> + * Copyright (C) 2017 NXP
>>> + *
>>> + * Authors: Peter Chen <peter.chen@nxp.com>
>>> + */
>>> +#ifndef __LINUX_CDNS3_HOST_EXPORT
>>> +#define __LINUX_CDNS3_HOST_EXPORT
>>> +
>>> +#ifdef CONFIG_USB_CDNS3_HOST
>>> +
>>> +int cdns3_host_init(struct cdns3 *cdns);
>>> +void cdns3_host_remove(struct cdns3 *cdns);
>>> +void cdns3_host_driver_init(void);
>>> +
>>> +#else
>>> +
>>> +static inline int cdns3_host_init(struct cdns3 *cdns)
>>> +{
>>> +	return -ENXIO;
>>> +}
>>> +
>>> +static inline void cdns3_host_remove(struct cdns3 *cdns) { }
>>> +static inline void cdns3_host_driver_init(void) {}
>>> +
>>> +#endif /* CONFIG_USB_CDNS3_HOST */
>>> +
>>> +#endif /* __LINUX_CDNS3_HOST_EXPORT */
>>> diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
>>> new file mode 100644
>>> index 000000000000..0dd47976cb28
>>> --- /dev/null
>>> +++ b/drivers/usb/cdns3/host.c
>>> @@ -0,0 +1,256 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Cadence USBSS DRD Driver - host side
>>> + *
>>> + * Copyright (C) 2018 Cadence Design Systems.
>>> + * Copyright (C) 2018 NXP
>>> + *
>>> + * Authors: Peter Chen <peter.chen@nxp.com>
>>> + *	    Pawel Laszczak <pawell@cadence.com>
>>> + */
>>> +
>>> +#include <linux/kernel.h>
>>> +#include <linux/device.h>
>>> +#include <linux/io.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/dma-mapping.h>
>>> +#include <linux/usb.h>
>>> +#include <linux/usb/hcd.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/usb/of.h>
>>> +
>>> +#include "../host/xhci.h"
>>> +#include "core.h"
>>> +#include "host-export.h"
>>> +
>>> +static struct hc_driver __read_mostly xhci_cdns3_hc_driver;
>>> +
>>> +static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci)
>>> +{
>>> +	/*
>>> +	 * As of now platform drivers don't provide MSI support so we ensure
>>> +	 * here that the generic code does not try to make a pci_dev from our
>>> +	 * dev struct in order to setup MSI
>>> +	 */
>>> +	xhci->quirks |= XHCI_PLAT;
>>> +}
>>> +
>>> +static int xhci_cdns3_setup(struct usb_hcd *hcd)
>>> +{
>>> +	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
>>> +	u32 command;
>>> +	int ret;
>>> +
>>> +	ret = xhci_gen_setup(hcd, xhci_cdns3_quirks);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	/* set usbcmd.EU3S */
>>> +	command = readl(&xhci->op_regs->command);
>>> +	command |= CMD_PM_INDEX;
>>> +	writel(command, &xhci->op_regs->command);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
>>> +	.extra_priv_size = sizeof(struct xhci_hcd),
>>> +	.reset = xhci_cdns3_setup,
>>> +};
>>> +
>>> +struct cdns3_host {
>>> +	struct device dev;
>>> +	struct usb_hcd *hcd;
>>> +};
>>> +
>>> +static irqreturn_t cdns3_host_irq(struct cdns3 *cdns)
>>> +{
>>> +	struct device *dev = cdns->host_dev;
>>> +	struct usb_hcd	*hcd;
>>> +
>>> +	if (dev)
>>> +		hcd = dev_get_drvdata(dev);
>>> +	else
>>> +		return IRQ_NONE;
>>> +
>>> +	if (hcd)
>>> +		return usb_hcd_irq(cdns->irq, hcd);
>>> +	else
>>> +		return IRQ_NONE;
>>
>> Why can't you just reuse the xhci-platform driver and let it manage the IRQ?
>> Since it is a shared IRQ, different drivers can request the same IRQ and return IRQ_NONE
>> if the IRQ wasn't from their device.
> 
> In device role the host part of controller is kept in reset, so driver can't read the host register. 
> Such solution allows driver to control access to host register. 
> So if driver has shared separate interrupt for host role then it has to check if controller work in 
> Host role.  

I understand what you mean. I think the issue here is that you are having the host ISR active
even when host role is stopped. This is the root cause of the problem.

When you stop host role, the host driver *must* unregister the ISR
and then place the host in reset.

This will happen correctly if you use platform_unregister_device() to unregister the
XHCI device in cdns3_host_stop().

> 
>>> +}
>>> +
>>> +static void cdns3_host_release(struct device *dev)
>>> +{
>>> +	struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
>>> +
>>> +	kfree(host);
>>> +}
>>> +
>>> +static int cdns3_host_start(struct cdns3 *cdns)
>>> +{
>>> +	struct cdns3_host *host;
>>> +	struct device *dev;
>>> +	struct device *sysdev;
>>> +	struct xhci_hcd	*xhci;
>>> +	int ret;
>>> +
>>> +	host = kzalloc(sizeof(*host), GFP_KERNEL);
>>> +	if (!host)
>>> +		return -ENOMEM;
>>> +
>>> +	dev = &host->dev;
>>> +	dev->release = cdns3_host_release;
>>> +	dev->parent = cdns->dev;
>>> +	dev_set_name(dev, "xhci-cdns3");
>>> +	cdns->host_dev = dev;
>>> +	ret = device_register(dev);
>>> +	if (ret)
>>> +		goto err1;
>>> +
>>> +	sysdev = cdns->dev;
>>> +	/* Try to set 64-bit DMA first */
>>> +	if (WARN_ON(!sysdev->dma_mask))
>>> +		/* Platform did not initialize dma_mask */
>>> +		ret = dma_coerce_mask_and_coherent(sysdev,
>>> +						   DMA_BIT_MASK(64));
>>> +	else
>>> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
>>> +
>>> +	/* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
>>> +	if (ret) {
>>> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +
>>> +	pm_runtime_set_active(dev);
>>> +	pm_runtime_no_callbacks(dev);
>>> +	pm_runtime_enable(dev);
>>> +	host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
>>> +				     dev_name(dev), NULL);
>>> +	if (!host->hcd) {
>>> +		ret = -ENOMEM;
>>> +		goto err2;
>>> +	}
>>> +
>>> +	host->hcd->regs = cdns->xhci_regs;
>>> +	host->hcd->rsrc_start = cdns->xhci_res->start;
>>> +	host->hcd->rsrc_len = resource_size(cdns->xhci_res);
>>> +
>>> +	device_wakeup_enable(host->hcd->self.controller);
>>> +	xhci = hcd_to_xhci(host->hcd);
>>> +
>>> +	xhci->main_hcd = host->hcd;
>>> +	xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
>>> +					    dev_name(dev), host->hcd);
>>> +	if (!xhci->shared_hcd) {
>>> +		ret = -ENOMEM;
>>> +		goto err3;
>>> +	}
>>> +
>>> +	host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
>>> +	xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
>>> +	ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
>>> +	if (ret)
>>> +		goto err4;
>>> +
>>> +	ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
>>> +	if (ret)
>>> +		goto err5;
>>> +
>>> +	device_set_wakeup_capable(dev, true);
>>
>> All this is being done by the xhci-plat.c
>>
>> You can make use of it by just creating a xhci-hcd platform device.
>>
>> e.g.
>> platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
>> platform_device_add_resources() to add IRQ and memory resource.
>> platform_device_add_properties() to add any quirks.
>> platform_device_add()
>>
> 
> If we do this in this way driver will not control the interrupt. 

Why should this driver control host interrupt when it doesn't
have access to HOST registers.

> This code  has written by Peter Chan and I am convinced 
> that this concept is only correct one.
> 
>>> +
>>> +	return 0;
>>> +
>>> +err5:
>>> +	usb_remove_hcd(host->hcd);
>>> +err4:
>>> +	usb_put_hcd(xhci->shared_hcd);
>>> +err3:
>>> +	usb_put_hcd(host->hcd);
>>> +err2:
>>> +	device_del(dev);
>>> +err1:
>>> +	put_device(dev);
>>> +	cdns->host_dev = NULL;
>>> +	return ret;
>>> +}
>>> +
>>> +static void cdns3_host_stop(struct cdns3 *cdns)
>>> +{
>>> +	struct device *dev = cdns->host_dev;
>>> +	struct xhci_hcd	*xhci;
>>> +	struct usb_hcd	*hcd;
>>> +
>>> +	if (dev) {
>>> +		hcd = dev_get_drvdata(dev);
>>> +		xhci = hcd_to_xhci(hcd);
>>> +		usb_remove_hcd(xhci->shared_hcd);
>>> +		usb_remove_hcd(hcd);
>>> +		synchronize_irq(cdns->irq);
>>> +		usb_put_hcd(xhci->shared_hcd);
>>> +		usb_put_hcd(hcd);
>>> +		cdns->host_dev = NULL;
>>> +		pm_runtime_set_suspended(dev);
>>> +		pm_runtime_disable(dev);
>>> +		device_del(dev);
>>> +		put_device(dev);
>>> +	}
>>
>> You can replace this with just
>> 	platform_device_unregister(xhci_dev);
>>
>>> +}
>>> +
>>> +#if CONFIG_PM
>>> +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
>>> +{
>>> +	struct device *dev = cdns->host_dev;
>>> +	struct xhci_hcd	*xhci;
>>> +
>>> +	if (!dev)
>>> +		return 0;
>>> +
>>> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
>>> +	return xhci_suspend(xhci, do_wakeup);
>>> +}
>>> +
>>> +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
>>> +{
>>> +	struct device *dev = cdns->host_dev;
>>> +	struct xhci_hcd	*xhci;
>>> +
>>> +	if (!dev)
>>> +		return 0;
>>> +
>>> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
>>> +	return xhci_resume(xhci, hibernated);
>>> +}
>>
>> These won't be required any more as xhci-plat is doing this.
>>
>>> +#endif /* CONFIG_PM */
>>> +
>>> +int cdns3_host_init(struct cdns3 *cdns)
>>> +{
>>> +	struct cdns3_role_driver *rdrv;
>>> +
>>> +	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
>>> +	if (!rdrv)
>>> +		return -ENOMEM;
>>> +
>>> +	rdrv->start	= cdns3_host_start;
>>> +	rdrv->stop	= cdns3_host_stop;
>>> +	rdrv->irq	= cdns3_host_irq;
>>> +#if CONFIG_PM
>>> +	rdrv->suspend	= cdns3_host_suspend;
>>> +	rdrv->resume	= cdns3_host_resume;
>>> +#endif /* CONFIG_PM */
>>> +	rdrv->name	= "host";
>>> +	cdns->roles[CDNS3_ROLE_HOST] = rdrv;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +void cdns3_host_remove(struct cdns3 *cdns)
>>> +{
>>> +	cdns3_host_stop(cdns);
>>
>> calling cdns3_host_stop() here can lead to problems as Controller might be in
>> peripheral mode at this point. The core driver needs to ensure that relevant role
>> is stopped before calling cdns3_host_remove().
>>
>> Here you need to unregister the role driver though.
>>
>> cdns->roles[CDNS3_ROLE_HOST] = NULL;
>>
> This function can be called only in host mode/role. It operate on host registers. 
> This checking is provided in core.c file. 
>>> +}
>>> +
>>> +void __init cdns3_host_driver_init(void)
>>> +{
>>> +	xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
>>> +}
>>>
>>

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-26  9:39           ` Roger Quadros
@ 2018-11-26 10:09             ` Pawel Laszczak
  2018-11-26 10:15               ` Roger Quadros
  0 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-26 10:09 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

>>>
>>> Pawel,
>>>
>>> On 26/11/18 09:23, Pawel Laszczak wrote:
>>>> Hi Roger,
>>>>
>>>>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>>>>> Patch adds supports for detecting Host/Device mode.
>>>>>> Controller has additional OTG register that allow
>>>>>> implement even whole OTG functionality.
>>>>>> At this moment patch adds support only for detecting
>>>>>> the appropriate mode based on strap pins and ID pin.
>>>>>>
>>>>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>>>>> ---
>>>>>>  drivers/usb/cdns3/Makefile |   2 +-
>>>>>>  drivers/usb/cdns3/core.c   |  27 +++--
>>>>>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>>>>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>>>>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>>>>>  create mode 100644 drivers/usb/cdns3/drd.c
>>>>>>  create mode 100644 drivers/usb/cdns3/drd.h
>>>>>>
>>>>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>>>>> index 02d25b23c5d3..e779b2a2f8eb 100644
>>>>>> --- a/drivers/usb/cdns3/Makefile
>>>>>> +++ b/drivers/usb/cdns3/Makefile
>>>>>> @@ -1,5 +1,5 @@
>>>>>>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>>>>
>>>>>> -cdns3-y					:= core.o
>>>>>> +cdns3-y					:= core.o drd.o
>>>>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>>>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>>>>> index f9055d4da67f..dbee4325da7f 100644
>>>>>> --- a/drivers/usb/cdns3/core.c
>>>>>> +++ b/drivers/usb/cdns3/core.c
>>>>>> @@ -17,6 +17,7 @@
>>>>>>
>>>>>>  #include "gadget.h"
>>>>>>  #include "core.h"
>>>>>> +#include "drd.h"
>>>>>>
>>>>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>>>>  {
>>>>>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>>>>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>>>>>  {
>>>>>>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>>>>> -		//TODO: implements selecting device/host mode
>>>>>> -		return CDNS3_ROLE_HOST;
>>>>>> +		if (cdns3_is_host(cdns))
>>>>>> +			return CDNS3_ROLE_HOST;
>>>>>> +		if (cdns3_is_device(cdns))
>>>>>> +			return CDNS3_ROLE_GADGET;
>>>>>>  	}
>>>>>>  	return cdns->roles[CDNS3_ROLE_HOST]
>>>>>>  		? CDNS3_ROLE_HOST
>>>>>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>>>>  	struct cdns3 *cdns = data;
>>>>>>  	irqreturn_t ret = IRQ_NONE;
>>>>>>
>>>>>> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
>>>>>> +		ret = cdns3_drd_irq(cdns);
>>>>>> +		if (ret == IRQ_HANDLED)
>>>>>> +			return ret;
>>>>>> +	}
>>>>>
>>>>> The kernel's shared IRQ model takes care of sharing the same interrupt
>>>>> between different devices and their drivers. You don't need to manually
>>>>> handle it here. Just let all 3 drivers do a request_irq() and have
>>>>> handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
>>>>> IRQ_NONE accordingly.
>>>>>
>>>>> Looks like you can do away with irq member of the role driver struct.
>>>>
>>>> Ok, I will split it into 3 separate part, but in this case, I additionally have to check the current
>>>> role in ISR function. Driver can't read host side registers when controller works in device role
>>>> and vice versa. One part of controller is kept in reset. Only DRD registers are common and are all accessible.
>>>>
>>>
>>> In which ISR do you need to check current role?
>>>
>>> I'm not sure if we are on the same page.
>>> Core (drd) driver shouldn't read host/device side registers. All 3 drivers,
>>> i.e. DRD(core), Host (xhci) and device (cdns3) should do a request_irq()
>>> and process their respective IRQ events.
>>
>> Yes, I understand.
>> I need to check this in cdns3_irq_handler_thread and cdns3_host_irq.
>>
>> Core (drd) has register that are always accessible.
>> Core (device)  - registers are available also in device mode
>> Core (host)  - registers  are available only in host mode
>>
>> So we can use separate request_irq  for drd, device and host side, but
>> we need ensure that in host side driver will not touch the device register.
>
>That should never happen as host side doesn't have visibility to device registers.

I meant the following scenario:
Assume that controller work in Device role and it raise interrupt for Device part. 
Now host is kept in reset. 
1. System receive interrupt 
2.  Kernel call drd_irq but this function return IRQ_NONE
3.  Kernel call cdns3_host_irq and driver has to read some interrupt register to check if 
     this event is addressed to him. In this moment we can have unknown behavior. 
4.  Kernel call cdns3_irq_handler  (device). This point probably will not happen because 
    It could have stuck at point 3. 

Ok, I think I understood. After changing role, the driver unregisters the interrupt function
used by previous role so point 3 never happen. 

I will try to check how it works.  

>>
>> We doesn't know the order in which  the system will call interrupts function related to
>>  the shared interrupt line.
>
>Order shouldn't matter. Each user will check its own events return IRQ_NONE if they
>didn't cause the IRQ.
>>
>
><snip>
>
>cheers,
>-roger
>
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

Thanks, 
Cheers,
Pawel

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

* Re: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-26 10:09             ` Pawel Laszczak
@ 2018-11-26 10:15               ` Roger Quadros
  0 siblings, 0 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-26 10:15 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

On 26/11/18 12:09, Pawel Laszczak wrote:
>>>>
>>>> Pawel,
>>>>
>>>> On 26/11/18 09:23, Pawel Laszczak wrote:
>>>>> Hi Roger,
>>>>>
>>>>>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>>>>>> Patch adds supports for detecting Host/Device mode.
>>>>>>> Controller has additional OTG register that allow
>>>>>>> implement even whole OTG functionality.
>>>>>>> At this moment patch adds support only for detecting
>>>>>>> the appropriate mode based on strap pins and ID pin.
>>>>>>>
>>>>>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>>>>>> ---
>>>>>>>  drivers/usb/cdns3/Makefile |   2 +-
>>>>>>>  drivers/usb/cdns3/core.c   |  27 +++--
>>>>>>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>>>>>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>>>>>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>>>>>>  create mode 100644 drivers/usb/cdns3/drd.c
>>>>>>>  create mode 100644 drivers/usb/cdns3/drd.h
>>>>>>>
>>>>>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>>>>>> index 02d25b23c5d3..e779b2a2f8eb 100644
>>>>>>> --- a/drivers/usb/cdns3/Makefile
>>>>>>> +++ b/drivers/usb/cdns3/Makefile
>>>>>>> @@ -1,5 +1,5 @@
>>>>>>>  obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>>>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>>>>>
>>>>>>> -cdns3-y					:= core.o
>>>>>>> +cdns3-y					:= core.o drd.o
>>>>>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>>>>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>>>>>> index f9055d4da67f..dbee4325da7f 100644
>>>>>>> --- a/drivers/usb/cdns3/core.c
>>>>>>> +++ b/drivers/usb/cdns3/core.c
>>>>>>> @@ -17,6 +17,7 @@
>>>>>>>
>>>>>>>  #include "gadget.h"
>>>>>>>  #include "core.h"
>>>>>>> +#include "drd.h"
>>>>>>>
>>>>>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>>>>>  {
>>>>>>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>>>>>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>>>>>>  {
>>>>>>>  	if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>>>>>> -		//TODO: implements selecting device/host mode
>>>>>>> -		return CDNS3_ROLE_HOST;
>>>>>>> +		if (cdns3_is_host(cdns))
>>>>>>> +			return CDNS3_ROLE_HOST;
>>>>>>> +		if (cdns3_is_device(cdns))
>>>>>>> +			return CDNS3_ROLE_GADGET;
>>>>>>>  	}
>>>>>>>  	return cdns->roles[CDNS3_ROLE_HOST]
>>>>>>>  		? CDNS3_ROLE_HOST
>>>>>>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>>>>>  	struct cdns3 *cdns = data;
>>>>>>>  	irqreturn_t ret = IRQ_NONE;
>>>>>>>
>>>>>>> +	if (cdns->dr_mode == USB_DR_MODE_OTG) {
>>>>>>> +		ret = cdns3_drd_irq(cdns);
>>>>>>> +		if (ret == IRQ_HANDLED)
>>>>>>> +			return ret;
>>>>>>> +	}
>>>>>>
>>>>>> The kernel's shared IRQ model takes care of sharing the same interrupt
>>>>>> between different devices and their drivers. You don't need to manually
>>>>>> handle it here. Just let all 3 drivers do a request_irq() and have
>>>>>> handlers check if the IRQ was theirs or not and return IRQ_HANDLED or
>>>>>> IRQ_NONE accordingly.
>>>>>>
>>>>>> Looks like you can do away with irq member of the role driver struct.
>>>>>
>>>>> Ok, I will split it into 3 separate part, but in this case, I additionally have to check the current
>>>>> role in ISR function. Driver can't read host side registers when controller works in device role
>>>>> and vice versa. One part of controller is kept in reset. Only DRD registers are common and are all accessible.
>>>>>
>>>>
>>>> In which ISR do you need to check current role?
>>>>
>>>> I'm not sure if we are on the same page.
>>>> Core (drd) driver shouldn't read host/device side registers. All 3 drivers,
>>>> i.e. DRD(core), Host (xhci) and device (cdns3) should do a request_irq()
>>>> and process their respective IRQ events.
>>>
>>> Yes, I understand.
>>> I need to check this in cdns3_irq_handler_thread and cdns3_host_irq.
>>>
>>> Core (drd) has register that are always accessible.
>>> Core (device)  - registers are available also in device mode
>>> Core (host)  - registers  are available only in host mode
>>>
>>> So we can use separate request_irq  for drd, device and host side, but
>>> we need ensure that in host side driver will not touch the device register.
>>
>> That should never happen as host side doesn't have visibility to device registers.
> 
> I meant the following scenario:
> Assume that controller work in Device role and it raise interrupt for Device part. 
> Now host is kept in reset. 
> 1. System receive interrupt 
> 2.  Kernel call drd_irq but this function return IRQ_NONE
> 3.  Kernel call cdns3_host_irq and driver has to read some interrupt register to check if 
>      this event is addressed to him. In this moment we can have unknown behavior. 

This is the problem. If device is not able to figure out its interrupt event then
it must unregister the ISR.

> 4.  Kernel call cdns3_irq_handler  (device). This point probably will not happen because 
>     It could have stuck at point 3. 
> 
> Ok, I think I understood. After changing role, the driver unregisters the interrupt function
> used by previous role so point 3 never happen. 
> 
> I will try to check how it works.  
> 

Cool! Thanks.

>>>
>>> We doesn't know the order in which  the system will call interrupts function related to
>>>  the shared interrupt line.
>>
>> Order shouldn't matter. Each user will check its own events return IRQ_NONE if they
>> didn't cause the IRQ.
>>>
>>
>> <snip>
>>

cheers,
-roger
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 06/15] usb:cdns3: Adds Host support
  2018-11-26  9:50       ` Roger Quadros
@ 2018-11-26 10:17         ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-26 10:17 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

>
>Hi,
>
>On 26/11/18 10:24, Pawel Laszczak wrote:
>>> EXTERNAL MAIL
>>>
>>>
>>> On 18/11/18 12:09, Pawel Laszczak wrote:
>>>> Patch adds host-export.h and host.c file and mplements functions that
>>>> allow to initialize, start and stop XHCI host driver.
>>>>
>>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>>> ---
>>>>  drivers/usb/cdns3/Kconfig       |  10 ++
>>>>  drivers/usb/cdns3/Makefile      |   1 +
>>>>  drivers/usb/cdns3/core.c        |   7 +-
>>>>  drivers/usb/cdns3/host-export.h |  30 ++++
>>>>  drivers/usb/cdns3/host.c        | 256 ++++++++++++++++++++++++++++++++
>>>>  5 files changed, 302 insertions(+), 2 deletions(-)
>>>>  create mode 100644 drivers/usb/cdns3/host-export.h
>>>>  create mode 100644 drivers/usb/cdns3/host.c
>>>>
>>>> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
>>>> index eb22a8692991..d92bc3d68eb0 100644
>>>> --- a/drivers/usb/cdns3/Kconfig
>>>> +++ b/drivers/usb/cdns3/Kconfig
>>>> @@ -10,6 +10,16 @@ config USB_CDNS3
>>>>
>>>>  if USB_CDNS3
>>>>
>>>> +config USB_CDNS3_HOST
>>>> +        bool "Cadence USB3 host controller"
>>>> +        depends on USB_XHCI_HCD
>>>> +        help
>>>> +          Say Y here to enable host controller functionality of the
>>>> +          cadence driver.
>>>> +
>>>> +          Host controller is compliance with XHCI so it will use
>>>> +          standard XHCI driver.
>>>> +
>>>>  config USB_CDNS3_PCI_WRAP
>>>>  	tristate "PCIe-based Platforms"
>>>>  	depends on USB_PCI && ACPI
>>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>>>> index e779b2a2f8eb..976117ba67ff 100644
>>>> --- a/drivers/usb/cdns3/Makefile
>>>> +++ b/drivers/usb/cdns3/Makefile
>>>> @@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>>>
>>>>  cdns3-y					:= core.o drd.o
>>>> +cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
>>>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>>>> index dbee4325da7f..4cb820be9ff3 100644
>>>> --- a/drivers/usb/cdns3/core.c
>>>> +++ b/drivers/usb/cdns3/core.c
>>>> @@ -17,6 +17,7 @@
>>>>
>>>>  #include "gadget.h"
>>>>  #include "core.h"
>>>> +#include "host-export.h"
>>>>  #include "drd.h"
>>>>
>>>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>> @@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
>>>>  	}
>>>>
>>>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>>>> -		//TODO: implements host initialization
>>>> +		if (cdns3_host_init(cdns))
>>>> +			dev_info(dev, "doesn't support host\n");
>>>
>>> dev_err()
>>>
>>> And you need to error out with error code.
>>
>> ok, but I assume that even if host returns error then we can use
>> only device role. Only when both functions return errors, then it's  a critical error
>> and function return error code.
>
>But at this point we are in OTG or HOST dr_mode and without host functional
>both will not function correctly. So we must error out so user can debug.

Ok,

>
>>>
>>>>  	}
>>>>
>>>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>>>> @@ -142,7 +144,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>>>
>>>>  static void cdns3_remove_roles(struct cdns3 *cdns)
>>>>  {
>>>> -	//TODO: implements this function
>>>
>>> if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST)
>>>
>>>> +	cdns3_host_remove(cdns);
>>>
>>> How about calling it cdns3_host_exit() to complement cdns3_host_init().
>>>
>>>>  }
>>>>
>>>>  static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>>>> @@ -410,6 +412,7 @@ static struct platform_driver cdns3_driver = {
>>>>
>>>>  static int __init cdns3_driver_platform_register(void)
>>>>  {
>>>> +	cdns3_host_driver_init();
>>>>  	return platform_driver_register(&cdns3_driver);
>>>>  }
>>>>  module_init(cdns3_driver_platform_register);
>>>> diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h
>>>> new file mode 100644
>>>> index 000000000000..f8f3b230b472
>>>> --- /dev/null
>>>> +++ b/drivers/usb/cdns3/host-export.h
>>>> @@ -0,0 +1,30 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +/*
>>>> + * Cadence USBSS DRD Driver -Host Export APIs
>>>> + *
>>>> + * Copyright (C) 2017 NXP
>>>> + *
>>>> + * Authors: Peter Chen <peter.chen@nxp.com>
>>>> + */
>>>> +#ifndef __LINUX_CDNS3_HOST_EXPORT
>>>> +#define __LINUX_CDNS3_HOST_EXPORT
>>>> +
>>>> +#ifdef CONFIG_USB_CDNS3_HOST
>>>> +
>>>> +int cdns3_host_init(struct cdns3 *cdns);
>>>> +void cdns3_host_remove(struct cdns3 *cdns);
>>>> +void cdns3_host_driver_init(void);
>>>> +
>>>> +#else
>>>> +
>>>> +static inline int cdns3_host_init(struct cdns3 *cdns)
>>>> +{
>>>> +	return -ENXIO;
>>>> +}
>>>> +
>>>> +static inline void cdns3_host_remove(struct cdns3 *cdns) { }
>>>> +static inline void cdns3_host_driver_init(void) {}
>>>> +
>>>> +#endif /* CONFIG_USB_CDNS3_HOST */
>>>> +
>>>> +#endif /* __LINUX_CDNS3_HOST_EXPORT */
>>>> diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c
>>>> new file mode 100644
>>>> index 000000000000..0dd47976cb28
>>>> --- /dev/null
>>>> +++ b/drivers/usb/cdns3/host.c
>>>> @@ -0,0 +1,256 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +/*
>>>> + * Cadence USBSS DRD Driver - host side
>>>> + *
>>>> + * Copyright (C) 2018 Cadence Design Systems.
>>>> + * Copyright (C) 2018 NXP
>>>> + *
>>>> + * Authors: Peter Chen <peter.chen@nxp.com>
>>>> + *	    Pawel Laszczak <pawell@cadence.com>
>>>> + */
>>>> +
>>>> +#include <linux/kernel.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/dma-mapping.h>
>>>> +#include <linux/usb.h>
>>>> +#include <linux/usb/hcd.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/usb/of.h>
>>>> +
>>>> +#include "../host/xhci.h"
>>>> +#include "core.h"
>>>> +#include "host-export.h"
>>>> +
>>>> +static struct hc_driver __read_mostly xhci_cdns3_hc_driver;
>>>> +
>>>> +static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci)
>>>> +{
>>>> +	/*
>>>> +	 * As of now platform drivers don't provide MSI support so we ensure
>>>> +	 * here that the generic code does not try to make a pci_dev from our
>>>> +	 * dev struct in order to setup MSI
>>>> +	 */
>>>> +	xhci->quirks |= XHCI_PLAT;
>>>> +}
>>>> +
>>>> +static int xhci_cdns3_setup(struct usb_hcd *hcd)
>>>> +{
>>>> +	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
>>>> +	u32 command;
>>>> +	int ret;
>>>> +
>>>> +	ret = xhci_gen_setup(hcd, xhci_cdns3_quirks);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	/* set usbcmd.EU3S */
>>>> +	command = readl(&xhci->op_regs->command);
>>>> +	command |= CMD_PM_INDEX;
>>>> +	writel(command, &xhci->op_regs->command);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = {
>>>> +	.extra_priv_size = sizeof(struct xhci_hcd),
>>>> +	.reset = xhci_cdns3_setup,
>>>> +};
>>>> +
>>>> +struct cdns3_host {
>>>> +	struct device dev;
>>>> +	struct usb_hcd *hcd;
>>>> +};
>>>> +
>>>> +static irqreturn_t cdns3_host_irq(struct cdns3 *cdns)
>>>> +{
>>>> +	struct device *dev = cdns->host_dev;
>>>> +	struct usb_hcd	*hcd;
>>>> +
>>>> +	if (dev)
>>>> +		hcd = dev_get_drvdata(dev);
>>>> +	else
>>>> +		return IRQ_NONE;
>>>> +
>>>> +	if (hcd)
>>>> +		return usb_hcd_irq(cdns->irq, hcd);
>>>> +	else
>>>> +		return IRQ_NONE;
>>>
>>> Why can't you just reuse the xhci-platform driver and let it manage the IRQ?
>>> Since it is a shared IRQ, different drivers can request the same IRQ and return IRQ_NONE
>>> if the IRQ wasn't from their device.
>>
>> In device role the host part of controller is kept in reset, so driver can't read the host register.
>> Such solution allows driver to control access to host register.
>> So if driver has shared separate interrupt for host role then it has to check if controller work in
>> Host role.
>
>I understand what you mean. I think the issue here is that you are having the host ISR active
>even when host role is stopped. This is the root cause of the problem.
>
>When you stop host role, the host driver *must* unregister the ISR
>and then place the host in reset.
>
>This will happen correctly if you use platform_unregister_device() to unregister the
>XHCI device in cdns3_host_stop().

Ok, now I understood you concept. I will test it. 
>>
>>>> +}
>>>> +
>>>> +static void cdns3_host_release(struct device *dev)
>>>> +{
>>>> +	struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
>>>> +
>>>> +	kfree(host);
>>>> +}
>>>> +
>>>> +static int cdns3_host_start(struct cdns3 *cdns)
>>>> +{
>>>> +	struct cdns3_host *host;
>>>> +	struct device *dev;
>>>> +	struct device *sysdev;
>>>> +	struct xhci_hcd	*xhci;
>>>> +	int ret;
>>>> +
>>>> +	host = kzalloc(sizeof(*host), GFP_KERNEL);
>>>> +	if (!host)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	dev = &host->dev;
>>>> +	dev->release = cdns3_host_release;
>>>> +	dev->parent = cdns->dev;
>>>> +	dev_set_name(dev, "xhci-cdns3");
>>>> +	cdns->host_dev = dev;
>>>> +	ret = device_register(dev);
>>>> +	if (ret)
>>>> +		goto err1;
>>>> +
>>>> +	sysdev = cdns->dev;
>>>> +	/* Try to set 64-bit DMA first */
>>>> +	if (WARN_ON(!sysdev->dma_mask))
>>>> +		/* Platform did not initialize dma_mask */
>>>> +		ret = dma_coerce_mask_and_coherent(sysdev,
>>>> +						   DMA_BIT_MASK(64));
>>>> +	else
>>>> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
>>>> +
>>>> +	/* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
>>>> +	if (ret) {
>>>> +		ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
>>>> +		if (ret)
>>>> +			return ret;
>>>> +	}
>>>> +
>>>> +	pm_runtime_set_active(dev);
>>>> +	pm_runtime_no_callbacks(dev);
>>>> +	pm_runtime_enable(dev);
>>>> +	host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
>>>> +				     dev_name(dev), NULL);
>>>> +	if (!host->hcd) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err2;
>>>> +	}
>>>> +
>>>> +	host->hcd->regs = cdns->xhci_regs;
>>>> +	host->hcd->rsrc_start = cdns->xhci_res->start;
>>>> +	host->hcd->rsrc_len = resource_size(cdns->xhci_res);
>>>> +
>>>> +	device_wakeup_enable(host->hcd->self.controller);
>>>> +	xhci = hcd_to_xhci(host->hcd);
>>>> +
>>>> +	xhci->main_hcd = host->hcd;
>>>> +	xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
>>>> +					    dev_name(dev), host->hcd);
>>>> +	if (!xhci->shared_hcd) {
>>>> +		ret = -ENOMEM;
>>>> +		goto err3;
>>>> +	}
>>>> +
>>>> +	host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
>>>> +	xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
>>>> +	ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
>>>> +	if (ret)
>>>> +		goto err4;
>>>> +
>>>> +	ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
>>>> +	if (ret)
>>>> +		goto err5;
>>>> +
>>>> +	device_set_wakeup_capable(dev, true);
>>>
>>> All this is being done by the xhci-plat.c
>>>
>>> You can make use of it by just creating a xhci-hcd platform device.
>>>
>>> e.g.
>>> platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
>>> platform_device_add_resources() to add IRQ and memory resource.
>>> platform_device_add_properties() to add any quirks.
>>> platform_device_add()
>>>
>>
>> If we do this in this way driver will not control the interrupt.
>
>Why should this driver control host interrupt when it doesn't
>have access to HOST registers.
>

You you are right. I doesn't have to. 

>> This code  has written by Peter Chan and I am convinced
>> that this concept is only correct one.
>>
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err5:
>>>> +	usb_remove_hcd(host->hcd);
>>>> +err4:
>>>> +	usb_put_hcd(xhci->shared_hcd);
>>>> +err3:
>>>> +	usb_put_hcd(host->hcd);
>>>> +err2:
>>>> +	device_del(dev);
>>>> +err1:
>>>> +	put_device(dev);
>>>> +	cdns->host_dev = NULL;
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void cdns3_host_stop(struct cdns3 *cdns)
>>>> +{
>>>> +	struct device *dev = cdns->host_dev;
>>>> +	struct xhci_hcd	*xhci;
>>>> +	struct usb_hcd	*hcd;
>>>> +
>>>> +	if (dev) {
>>>> +		hcd = dev_get_drvdata(dev);
>>>> +		xhci = hcd_to_xhci(hcd);
>>>> +		usb_remove_hcd(xhci->shared_hcd);
>>>> +		usb_remove_hcd(hcd);
>>>> +		synchronize_irq(cdns->irq);
>>>> +		usb_put_hcd(xhci->shared_hcd);
>>>> +		usb_put_hcd(hcd);
>>>> +		cdns->host_dev = NULL;
>>>> +		pm_runtime_set_suspended(dev);
>>>> +		pm_runtime_disable(dev);
>>>> +		device_del(dev);
>>>> +		put_device(dev);
>>>> +	}
>>>
>>> You can replace this with just
>>> 	platform_device_unregister(xhci_dev);
>>>
>>>> +}
>>>> +
>>>> +#if CONFIG_PM
>>>> +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
>>>> +{
>>>> +	struct device *dev = cdns->host_dev;
>>>> +	struct xhci_hcd	*xhci;
>>>> +
>>>> +	if (!dev)
>>>> +		return 0;
>>>> +
>>>> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
>>>> +	return xhci_suspend(xhci, do_wakeup);
>>>> +}
>>>> +
>>>> +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
>>>> +{
>>>> +	struct device *dev = cdns->host_dev;
>>>> +	struct xhci_hcd	*xhci;
>>>> +
>>>> +	if (!dev)
>>>> +		return 0;
>>>> +
>>>> +	xhci = hcd_to_xhci(dev_get_drvdata(dev));
>>>> +	return xhci_resume(xhci, hibernated);
>>>> +}
>>>
>>> These won't be required any more as xhci-plat is doing this.
>>>
>>>> +#endif /* CONFIG_PM */
>>>> +
>>>> +int cdns3_host_init(struct cdns3 *cdns)
>>>> +{
>>>> +	struct cdns3_role_driver *rdrv;
>>>> +
>>>> +	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
>>>> +	if (!rdrv)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	rdrv->start	= cdns3_host_start;
>>>> +	rdrv->stop	= cdns3_host_stop;
>>>> +	rdrv->irq	= cdns3_host_irq;
>>>> +#if CONFIG_PM
>>>> +	rdrv->suspend	= cdns3_host_suspend;
>>>> +	rdrv->resume	= cdns3_host_resume;
>>>> +#endif /* CONFIG_PM */
>>>> +	rdrv->name	= "host";
>>>> +	cdns->roles[CDNS3_ROLE_HOST] = rdrv;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +void cdns3_host_remove(struct cdns3 *cdns)
>>>> +{
>>>> +	cdns3_host_stop(cdns);
>>>
>>> calling cdns3_host_stop() here can lead to problems as Controller might be in
>>> peripheral mode at this point. The core driver needs to ensure that relevant role
>>> is stopped before calling cdns3_host_remove().
>>>
>>> Here you need to unregister the role driver though.
>>>
>>> cdns->roles[CDNS3_ROLE_HOST] = NULL;
>>>
>> This function can be called only in host mode/role. It operate on host registers.
>> This checking is provided in core.c file.
>>>> +}
>>>> +
>>>> +void __init cdns3_host_driver_init(void)
>>>> +{
>>>> +	xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
>>>> +}
>>>>
>>>
>
>cheers,
>-roger
>
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-26  7:23     ` Pawel Laszczak
  2018-11-26  8:07       ` Roger Quadros
@ 2018-11-27 11:29       ` Pawel Laszczak
  2018-11-27 12:10         ` Roger Quadros
  1 sibling, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-27 11:29 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi Roger

>>> Patch adds supports for detecting Host/Device mode.
>>> +
>>> +static int cdns3_otg_get_id(struct cdns3 *cdns)
>>> +{
>>> +	int id;
>>> +
>>> +	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
>>> +	dev_dbg(cdns->dev, "OTG ID: %d", id);
>>> +	return id;
>>> +}
>>> +
>>> +int cdns3_is_host(struct cdns3 *cdns)
>>> +{
>>> +	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
>>> +		return 1;
>>
>>Why do you need this?
>
>I assumed that some SoC could have cut DRD /OTG and Device or Host part.
>In such case the driver cannot be based on ID pin.
>For only HOST it's not a problem because
>the standard XHCI driver will be used.  Probably I will remove this fragment.

I've removed this condition but it is necessary and I've  restored it again. 
When driver works in only HOST mode then ID is always 0. 
For current_dr_mode == USB_DR_MODE_HOST driver has to just simple 
returns 1.  
current_dr_mode  can be changed from user space depending on dr_mode field. 

I have the additional question. Because I have many changes in source code if I should 
post the next RFC PATCH v3 or should I wait for comments for rest patches ?

>>
>>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>>> +		if (!cdns3_otg_get_id(cdns))
>>> +			return 1;
>>> +
>>> +	return 0;
>>> +}
>>> +
Thanks,
Cheers,
Pawel

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

* Re: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-27 11:29       ` Pawel Laszczak
@ 2018-11-27 12:10         ` Roger Quadros
  0 siblings, 0 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-27 12:10 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Pawel,

On 27/11/18 13:29, Pawel Laszczak wrote:
> Hi Roger
> 
>>>> Patch adds supports for detecting Host/Device mode.
>>>> +
>>>> +static int cdns3_otg_get_id(struct cdns3 *cdns)
>>>> +{
>>>> +	int id;
>>>> +
>>>> +	id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
>>>> +	dev_dbg(cdns->dev, "OTG ID: %d", id);
>>>> +	return id;
>>>> +}
>>>> +
>>>> +int cdns3_is_host(struct cdns3 *cdns)
>>>> +{
>>>> +	if (cdns->current_dr_mode == USB_DR_MODE_HOST)
>>>> +		return 1;
>>>
>>> Why do you need this?
>>
>> I assumed that some SoC could have cut DRD /OTG and Device or Host part.
>> In such case the driver cannot be based on ID pin.
>> For only HOST it's not a problem because
>> the standard XHCI driver will be used.  Probably I will remove this fragment.
> 
> I've removed this condition but it is necessary and I've  restored it again. 
> When driver works in only HOST mode then ID is always 0. 
> For current_dr_mode == USB_DR_MODE_HOST driver has to just simple 
> returns 1.  
> current_dr_mode  can be changed from user space depending on dr_mode field. 

OK.

> 
> I have the additional question. Because I have many changes in source code if I should 
> post the next RFC PATCH v3 or should I wait for comments for rest patches ?

I will need a day to review the remaining patches. So maybe it is better to wait?

> 
>>>
>>>> +	else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>>>> +		if (!cdns3_otg_get_id(cdns))
>>>> +			return 1;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
> Thanks,
> Cheers,
> Pawel
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization.
  2018-11-18 10:09 ` [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization Pawel Laszczak
@ 2018-11-28 11:34   ` Roger Quadros
  2018-11-28 11:40     ` Felipe Balbi
  2018-11-30 14:36     ` Pawel Laszczak
  0 siblings, 2 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-28 11:34 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree, Felipe Balbi
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

+Felipe.

Pawel,

Please copy Felipe Balbi as he maintains the USB gadget stack.

On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch implements a set of functions responsible for initialization,
> configuration, starting and stopping device mode.
> This patch also adds new ep0.c that holds all functions related
> to endpoint 0.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/Kconfig         |  10 +
>  drivers/usb/cdns3/Makefile        |   1 +
>  drivers/usb/cdns3/core.c          |   5 +-
>  drivers/usb/cdns3/ep0.c           | 105 ++++++++
>  drivers/usb/cdns3/gadget-export.h |  27 +++
>  drivers/usb/cdns3/gadget.c        | 390 ++++++++++++++++++++++++++++++
>  drivers/usb/cdns3/gadget.h        |   4 +
>  7 files changed, 541 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/usb/cdns3/ep0.c
>  create mode 100644 drivers/usb/cdns3/gadget-export.h
>  create mode 100644 drivers/usb/cdns3/gadget.c
> 
> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
> index d92bc3d68eb0..b7d71b5c4f60 100644
> --- a/drivers/usb/cdns3/Kconfig
> +++ b/drivers/usb/cdns3/Kconfig
> @@ -10,6 +10,16 @@ config USB_CDNS3
>  
>  if USB_CDNS3
>  
> +config USB_CDNS3_GADGET
> +        bool "Cadence USB3 device controller"
> +        depends on USB_GADGET
> +        help
> +          Say Y here to enable device controller functionality of the
> +          cadence USBSS-DEV driver.
> +
> +          This controller support FF, HS and SS mode. It doeasn't support

s/support/supports
s/doeasn't/doesn't

> +          LS and SSP mode
> +
>  config USB_CDNS3_HOST
>          bool "Cadence USB3 host controller"
>          depends on USB_XHCI_HCD
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> index 976117ba67ff..bea6173bf37f 100644
> --- a/drivers/usb/cdns3/Makefile
> +++ b/drivers/usb/cdns3/Makefile
> @@ -2,5 +2,6 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>  
>  cdns3-y					:= core.o drd.o
> +cdns3-$(CONFIG_USB_CDNS3_GADGET)	+= gadget.o ep0.o
>  cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> index 4cb820be9ff3..1fa233415901 100644
> --- a/drivers/usb/cdns3/core.c
> +++ b/drivers/usb/cdns3/core.c
> @@ -18,6 +18,7 @@
>  #include "gadget.h"
>  #include "core.h"
>  #include "host-export.h"
> +#include "gadget-export.h"
>  #include "drd.h"
>  
>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> @@ -104,7 +105,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
>  	}
>  
>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
> -		//TODO: implements device initialization
> +		if (cdns3_gadget_init(cdns))
> +			dev_info(dev, "doesn't support gadget\n");

dev_err() and we should should error out with error code returned by cdns3_gadget_init().

>  	}
>  
>  	if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
> @@ -144,6 +146,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>  
>  static void cdns3_remove_roles(struct cdns3 *cdns)
>  {

if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL)

> +	cdns3_gadget_remove(cdns);
>  	cdns3_host_remove(cdns);
>  }
>  
> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
> new file mode 100644
> index 000000000000..c08d02665f9d
> --- /dev/null
> +++ b/drivers/usb/cdns3/ep0.c
> @@ -0,0 +1,105 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver - gadget side.
> + *
> + * Copyright (C) 2018 Cadence Design Systems.
> + * Copyright (C) 2017 NXP
> + *
> + * Authors: Pawel Jez <pjez@cadence.com>,
> + *          Pawel Laszczak <pawell@cadence.com>
> + *	    Peter Chen <peter.chen@nxp.com>
> + */
> +
> +#include "gadget.h"
> +
> +static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +	.bmAttributes =	USB_ENDPOINT_XFER_CONTROL,
> +};
> +
> +static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
> +{
> +	//TODO: Implements this function
> +}
> +
> +/**
> + * cdns3_ep0_config - Configures default endpoint
> + * @priv_dev: extended gadget object
> + *
> + * Functions sets parameters: maximal packet size and enables interrupts
> + */
> +void cdns3_ep0_config(struct cdns3_device *priv_dev)
> +{
> +	struct cdns3_usb_regs __iomem *regs;
> +	u32 max_packet_size = 64;
> +
> +	regs = priv_dev->regs;
> +
> +	if (priv_dev->gadget.speed == USB_SPEED_SUPER)
> +		max_packet_size = 512;

is gadget.speed known at this point? I think it will only be known after a connection
is made.

> +
> +	if (priv_dev->ep0_request) {
> +		list_del_init(&priv_dev->ep0_request->list);
> +		priv_dev->ep0_request = NULL;
> +	}
> +
> +	priv_dev->gadget.ep0->maxpacket = max_packet_size;
> +	cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
> +
> +	/* init ep out */
> +	cdns3_select_ep(priv_dev, USB_DIR_OUT);
> +
> +	writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
> +	       &regs->ep_cfg);
> +
> +	writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN,
> +	       &regs->ep_sts_en);
> +
> +	/* init ep in */
> +	cdns3_select_ep(priv_dev, USB_DIR_IN);
> +
> +	writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
> +	       &regs->ep_cfg);
> +
> +	writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, &regs->ep_sts_en);
> +
> +	cdns3_set_register_bit(&regs->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS);
> +	cdns3_prepare_setup_packet(priv_dev);

What happens if gadget.speed is USB_SPEED_SUPER, but was connected to a high-speed host?
Are you updating the max_packet_size on connection done?

> +}
> +
> +/**
> + * cdns3_init_ep0 Initializes software endpoint 0 of gadget
> + * @cdns3: extended gadget object
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +int cdns3_init_ep0(struct cdns3_device *priv_dev)
> +{
> +	struct cdns3_endpoint *ep0;
> +
> +	ep0 = devm_kzalloc(&priv_dev->dev, sizeof(struct cdns3_endpoint),
> +			   GFP_KERNEL);
> +
> +	if (!ep0)
> +		return -ENOMEM;
> +
> +	ep0->cdns3_dev = priv_dev;
> +	sprintf(ep0->name, "ep0");
> +
> +	/* fill linux fields */
> +	//TODO: implements cdns3_gadget_ep0_ops object
> +	//ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
> +	ep0->endpoint.maxburst = 1;
> +	usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
> +	ep0->endpoint.address = 0;
> +	ep0->endpoint.caps.type_control = 1;
> +	ep0->endpoint.caps.dir_in = 1;
> +	ep0->endpoint.caps.dir_out = 1;
> +	ep0->endpoint.name = ep0->name;
> +	ep0->endpoint.desc = &cdns3_gadget_ep0_desc;
> +	priv_dev->gadget.ep0 = &ep0->endpoint;
> +	INIT_LIST_HEAD(&ep0->request_list);
> +
> +	return 0;
> +}
> diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h
> new file mode 100644
> index 000000000000..257e5e0eef31
> --- /dev/null
> +++ b/drivers/usb/cdns3/gadget-export.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence USBSS DRD Driver -Gadget Export APIs
> + *
> + * Copyright (C) 2017 NXP
> + *
> + * Authors: Peter Chen <peter.chen@nxp.com>
> + */
> +#ifndef __LINUX_CDNS3_GADGET_EXPORT
> +#define __LINUX_CDNS3_GADGET_EXPORT
> +
> +#ifdef CONFIG_USB_CDNS3_GADGET
> +
> +int cdns3_gadget_init(struct cdns3 *cdns);
> +void cdns3_gadget_remove(struct cdns3 *cdns);
> +#else
> +
> +static inline int cdns3_gadget_init(struct cdns3 *cdns)
> +{
> +	return -ENXIO;
> +}
> +
> +static inline void cdns3_gadget_remove(struct cdns3 *cdns) { }
> +
> +#endif
> +
> +#endif /* __LINUX_CDNS3_GADGET_EXPORT */
> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
> new file mode 100644
> index 000000000000..376b68b13d1b
> --- /dev/null
> +++ b/drivers/usb/cdns3/gadget.c
> @@ -0,0 +1,390 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver - gadget side.
> + *
> + * Copyright (C) 2018 Cadence Design Systems.
> + * Copyright (C) 2017 NXP
> + *
> + * Authors: Pawel Jez <pjez@cadence.com>,
> + *          Pawel Laszczak <pawell@cadence.com>
> + *	    Peter Chen <peter.chen@nxp.com>
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/usb/gadget.h>
> +
> +#include "core.h"
> +#include "gadget-export.h"
> +#include "gadget.h"
> +
> +/**
> + * cdns3_set_register_bit - set bit in given register.
> + * @ptr: address of device controller register to be read and changed
> + * @mask: bits requested to set
> + */
> +void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
> +{
> +	mask = readl(ptr) | mask;
> +	writel(mask, ptr);
> +}

static inline?
I'd get rid of this function if possible. You are using it only once
and it is easier to read code if setting the bitmask is done plainly
instead of hiding it behind a function.

> +
> +/**
> + * select_ep - selects endpoint
> + * @priv_dev:  extended gadget object
> + * @ep: endpoint address
> + */
> +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
> +{
> +	if (priv_dev->selected_ep == ep)
> +		return;

I didn't understand the purpose of this function. You can only select the EP once?

> +
> +	dev_dbg(&priv_dev->dev, "Ep sel: 0x%02x\n", ep);

You should move to use trace-points instead of dev_dbg.

> +	priv_dev->selected_ep = ep;
> +	writel(ep, &priv_dev->regs->ep_sel);
> +}
> +
> +/**
> + * cdns3_irq_handler - irq line interrupt handler
> + * @cdns: cdns3 instance
> + *
> + * Returns IRQ_HANDLED when interrupt raised by USBSS_DEV,
> + * IRQ_NONE when interrupt raised by other device connected
> + * to the irq line
> + */
> +static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
> +{
> +	irqreturn_t ret = IRQ_NONE;
> +	//TODO: implements this function
> +	return ret;
> +}
> +
> +static void cdns3_gadget_config(struct cdns3_device *priv_dev)
> +{
> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
> +
> +	cdns3_ep0_config(priv_dev);
> +
> +	/* enable interrupts for endpoint 0 (in and out) */
> +	writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, &regs->ep_ien);
> +
> +	/* enable generic interrupt*/
> +	writel(USB_IEN_INIT, &regs->usb_ien);
> +	writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, &regs->usb_conf);
> +	writel(USB_CONF_DMULT, &regs->usb_conf);
> +	writel(USB_CONF_DEVEN, &regs->usb_conf);

If you are enabling interrupts in this patch you should handle them in the ISR.

> +}
> +
> +/**
> + * cdns3_init_ep Initializes software endpoints of gadget
> + * @cdns3: extended gadget object
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +static int cdns3_init_ep(struct cdns3_device *priv_dev)
> +{
> +	u32 ep_enabled_reg, iso_ep_reg;
> +	struct cdns3_endpoint *priv_ep;
> +	int found_endpoints = 0;
> +	int ep_dir, ep_number;
> +	u32 ep_mask;
> +	int i;
> +
> +	/* Read it from USB_CAP3 to USB_CAP5 */
> +	ep_enabled_reg = readl(&priv_dev->regs->usb_cap3);
> +	iso_ep_reg = readl(&priv_dev->regs->usb_cap4);
> +
> +	dev_dbg(&priv_dev->dev, "Initializing non-zero endpoints\n");
> +
> +	for (i = 0; i < USB_SS_ENDPOINTS_MAX_COUNT; i++) {

CDNS3_USB_SS_ENDPOINTS_MAX_COUNT

> +		ep_number = (i / 2) + 1;
> +		ep_dir = i % 2;
> +		ep_mask = BIT((16 * ep_dir) + ep_number);
> +
> +		if (!(ep_enabled_reg & ep_mask))
> +			continue;
> +
> +		priv_ep = devm_kzalloc(&priv_dev->dev, sizeof(*priv_ep),
> +				       GFP_KERNEL);
> +		if (!priv_ep)
> +			return -ENOMEM;
> +
> +		/* set parent of endpoint object */
> +		priv_ep->cdns3_dev = priv_dev;
> +		priv_dev->eps[found_endpoints++] = priv_ep;
> +
> +		snprintf(priv_ep->name, sizeof(priv_ep->name), "ep%d%s",
> +			 ep_number, !!ep_dir ? "in" : "out");
> +		priv_ep->endpoint.name = priv_ep->name;
> +
> +		usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
> +					   ENDPOINT_MAX_PACKET_LIMIT);

CDNS3_ENDPOINT_MAX_PACKET_LIMIT

> +		priv_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;

CDNS3_ENDPOINT_MAX_STREAMS

> +		//TODO: Add implementation of cdns3_gadget_ep_ops
> +		//priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
> +		if (ep_dir)
> +			priv_ep->endpoint.caps.dir_in = 1;
> +		else
> +			priv_ep->endpoint.caps.dir_out = 1;
> +
> +		if (iso_ep_reg & ep_mask)
> +			priv_ep->endpoint.caps.type_iso = 1;
> +
> +		priv_ep->endpoint.caps.type_bulk = 1;
> +		priv_ep->endpoint.caps.type_int = 1;
> +		priv_ep->endpoint.maxburst = CDNS3_EP_BUF_SIZE - 1;
> +
> +		priv_ep->flags = 0;
> +
> +		dev_info(&priv_dev->dev, "Initialized  %s support: %s %s\n",
> +			 priv_ep->name,
> +			 priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "",
> +			 priv_ep->endpoint.caps.type_iso ? "ISO" : "");
> +
> +		list_add_tail(&priv_ep->endpoint.ep_list,
> +			      &priv_dev->gadget.ep_list);
> +		INIT_LIST_HEAD(&priv_ep->request_list);
> +		INIT_LIST_HEAD(&priv_ep->ep_match_pending_list);
> +	}
> +
> +	priv_dev->ep_nums = found_endpoints;
> +	return 0;
> +}
> +
> +static void cdns3_gadget_release(struct device *dev)
> +{
> +	struct cdns3_device *priv_dev;
> +
> +	priv_dev = container_of(dev, struct cdns3_device, dev);
> +	kfree(priv_dev);
> +}
> +
> +static int __cdns3_gadget_init(struct cdns3 *cdns)
> +{
> +	struct cdns3_device *priv_dev;
> +	struct device *dev;
> +	int ret;
> +
> +	priv_dev = kzalloc(sizeof(*priv_dev), GFP_KERNEL);
> +	if (!priv_dev)
> +		return -ENOMEM;
> +
> +	dev = &priv_dev->dev;
> +	dev->release = cdns3_gadget_release;
> +	dev->parent = cdns->dev;
> +	dev_set_name(dev, "gadget-cdns3");
> +	cdns->gadget_dev = dev;
> +
> +	priv_dev->sysdev = cdns->dev;
> +	ret = device_register(dev);

Why do you need to create this dummy device? you could just use cdns3->dev.

> +	if (ret)
> +		goto err1;
> +
> +	priv_dev->regs = cdns->dev_regs;
> +
> +	/* fill gadget fields */
> +	priv_dev->gadget.max_speed = USB_SPEED_SUPER;
> +	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> +	//TODO: Add implementation of cdns3_gadget_ops
> +	//priv_dev->gadget.ops = &cdns3_gadget_ops;
> +	priv_dev->gadget.name = "usb-ss-gadget";
> +	priv_dev->gadget.sg_supported = 1;
> +	priv_dev->is_connected = 0;
> +
> +	spin_lock_init(&priv_dev->lock);
> +
> +	priv_dev->in_standby_mode = 1;
> +
> +	/* initialize endpoint container */
> +	INIT_LIST_HEAD(&priv_dev->gadget.ep_list);
> +	INIT_LIST_HEAD(&priv_dev->ep_match_list);
> +
> +	ret = cdns3_init_ep0(priv_dev);
> +	if (ret) {
> +		dev_err(dev, "Failed to create endpoint 0\n");
> +		ret = -ENOMEM;

why not just use the ret as is.

> +		goto err2;
> +	}
> +
> +	ret = cdns3_init_ep(priv_dev);
> +	if (ret) {
> +		dev_err(dev, "Failed to create non zero endpoints\n");
> +		ret = -ENOMEM;

here too

> +		goto err2;
> +	}
> +
> +	/* allocate memory for default endpoint TRB */
> +	priv_dev->trb_ep0 = dma_alloc_coherent(priv_dev->sysdev, 24,

what is 24?
In error path you are using TRB_SIZE * 2. Should we be using that here too?


> +					       &priv_dev->trb_ep0_dma, GFP_DMA);
> +	if (!priv_dev->trb_ep0) {
> +		dev_err(dev, "Failed to allocate memory for ep0 TRB\n");
> +		ret = -ENOMEM;
> +		goto err2;
> +	}
> +
> +	/* allocate memory for setup packet buffer */
> +	priv_dev->setup = dma_alloc_coherent(priv_dev->sysdev, 8,

is 8 enough for all use cases? What about vendor specific setup requests?

> +					     &priv_dev->setup_dma, GFP_DMA);
> +	if (!priv_dev->setup) {
> +		dev_err(dev, "Failed to allocate memory for SETUP buffer\n");
> +		ret = -ENOMEM;
> +		goto err3;
> +	}
> +
> +	dev_dbg(dev, "Device Controller version: %08x\n",
> +		readl(&priv_dev->regs->usb_cap6));
> +	dev_dbg(dev, "USB Capabilities:: %08x\n",
> +		readl(&priv_dev->regs->usb_cap1));
> +	dev_dbg(dev, "On-Chip memory cnfiguration: %08x\n",
> +		readl(&priv_dev->regs->usb_cap2));
> +
> +	/* add USB gadget device */
> +	ret = usb_add_gadget_udc(&priv_dev->dev, &priv_dev->gadget);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to register USB device controller\n");
> +		goto err4;
> +	}
> +
> +	priv_dev->zlp_buf = kzalloc(ENDPOINT_ZLP_BUF_SIZE, GFP_KERNEL);
> +	if (!priv_dev->zlp_buf) {
> +		ret = -ENOMEM;
> +		goto err4;
> +	}

how about allocating zlp_buf before usb_add_gadget_udc()?

> +
> +	return 0;
> +err4:
> +	dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup,
> +			  priv_dev->setup_dma);
> +err3:
> +	dma_free_coherent(priv_dev->sysdev, TRB_SIZE * 2, priv_dev->trb_ep0,
> +			  priv_dev->trb_ep0_dma);
> +err2:
> +	device_del(dev);
> +err1:
> +	put_device(dev);
> +	cdns->gadget_dev = NULL;
> +	return ret;
> +}
> +
> +/**
> + * cdns3_gadget_remove: parent must call this to remove UDC
> + *
> + * cdns: cdns3 instance
> + */
> +void cdns3_gadget_remove(struct cdns3 *cdns)

how about calling this cdns3_gadget_exit() to complememnt cdns3_gadget_init()

> +{
> +	struct cdns3_device *priv_dev;
> +
> +	if (!cdns->roles[CDNS3_ROLE_GADGET])
> +		return;

why this check? It will lead to an unbalanced exit() and future init().

> +
> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
> +	usb_del_gadget_udc(&priv_dev->gadget);
> +	dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup,
> +			  priv_dev->setup_dma);
> +	dma_free_coherent(priv_dev->sysdev, TRB_SIZE * 2, priv_dev->trb_ep0,
> +			  priv_dev->trb_ep0_dma);

You need to free all memory allocation done in cdns3_init_ep0() and cdns3_init_ep()
else we eat memory on every role switch if we plan on getting rid of the dummy gadget_dev.

> +	device_unregister(cdns->gadget_dev);
> +	cdns->gadget_dev = NULL;
> +	kfree(priv_dev->zlp_buf);
> +}
> +
> +static int cdns3_gadget_start(struct cdns3 *cdns)
> +{
> +	struct cdns3_device *priv_dev = container_of(cdns->gadget_dev,
> +			struct cdns3_device, dev);
> +	unsigned long flags;
> +
> +	pm_runtime_get_sync(cdns->dev);

This is another reason why we shouldn't be creating a dummy gadget_dev.
PM is tied to cdns->dev.

> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	priv_dev->start_gadget = 1;
> +
> +	if (!priv_dev->gadget_driver) {
> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
> +		return 0;
> +	}
> +
> +	cdns3_gadget_config(priv_dev);
> +	priv_dev->in_standby_mode = 0;
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return 0;
> +}
> +
> +static void __cdns3_gadget_stop(struct cdns3 *cdns)
> +{
> +	struct cdns3_device *priv_dev;
> +	unsigned long flags;
> +
> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
> +
> +	if (priv_dev->gadget_driver)
> +		priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
> +
> +	usb_gadget_disconnect(&priv_dev->gadget);
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> +
> +	/* disable interrupt for device */
> +	writel(0, &priv_dev->regs->usb_ien);
> +	writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
> +	priv_dev->start_gadget = 0;
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +}
> +
> +static void cdns3_gadget_stop(struct cdns3 *cdns)
> +{
> +	if (cdns->role == CDNS3_ROLE_GADGET)
> +		__cdns3_gadget_stop(cdns);

Why worry about role here? That should be job of core/drd driver.

> +
> +	pm_runtime_mark_last_busy(cdns->dev);
> +	pm_runtime_put_autosuspend(cdns->dev);
> +}
> +
> +static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup)
> +{
> +	__cdns3_gadget_stop(cdns);
> +	return 0;
> +}
> +
> +static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated)
> +{
> +	struct cdns3_device *priv_dev;
> +	unsigned long flags;
> +
> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	priv_dev->start_gadget = 1;

who is using this start_gadget flag?

> +	if (!priv_dev->gadget_driver) {
> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
> +		return 0;
> +	}
> +
> +	cdns3_gadget_config(priv_dev);
> +	priv_dev->in_standby_mode = 0;
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return 0;
> +}
> +
> +/**
> + * cdns3_gadget_init - initialize device structure
> + *
> + * cdns: cdns3 instance
> + *
> + * This function initializes the gadget.
> + */
> +int cdns3_gadget_init(struct cdns3 *cdns)
> +{
> +	struct cdns3_role_driver *rdrv;
> +
> +	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
> +	if (!rdrv)
> +		return -ENOMEM;
> +
> +	rdrv->start	= cdns3_gadget_start;
> +	rdrv->stop	= cdns3_gadget_stop;

Why not use gadget_init()/gadget_exit() here? That sounds much cleaner
as you won't have ISR's active after a role stop.

> +	rdrv->suspend	= cdns3_gadget_suspend;
> +	rdrv->resume	= cdns3_gadget_resume;
> +	rdrv->irq	= cdns3_irq_handler_thread;
> +	rdrv->name	= "gadget";
> +	cdns->roles[CDNS3_ROLE_GADGET] = rdrv;
> +	return __cdns3_gadget_init(cdns);
> +}
> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
> index 75ca6214e79a..3b0d4d2e4831 100644
> --- a/drivers/usb/cdns3/gadget.h
> +++ b/drivers/usb/cdns3/gadget.h
> @@ -1068,4 +1068,8 @@ struct cdns3_device {
>  	struct usb_request		*pending_status_request;
>  };
>  
> +void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
> +int cdns3_init_ep0(struct cdns3_device *priv_dev);
> +void cdns3_ep0_config(struct cdns3_device *priv_dev);
> +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>  #endif /* __LINUX_CDNS3_GADGET */
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization.
  2018-11-28 11:34   ` Roger Quadros
@ 2018-11-28 11:40     ` Felipe Balbi
  2018-11-30  4:20       ` PETER CHEN
  2018-11-30 14:36     ` Pawel Laszczak
  1 sibling, 1 reply; 85+ messages in thread
From: Felipe Balbi @ 2018-11-28 11:40 UTC (permalink / raw)
  To: Roger Quadros, Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul

[-- Attachment #1: Type: text/plain, Size: 978 bytes --]


Hi,

Roger Quadros <rogerq@ti.com> writes:
>> +static void cdns3_gadget_config(struct cdns3_device *priv_dev)
>> +{
>> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>> +
>> +	cdns3_ep0_config(priv_dev);
>> +
>> +	/* enable interrupts for endpoint 0 (in and out) */
>> +	writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, &regs->ep_ien);
>> +
>> +	/* enable generic interrupt*/
>> +	writel(USB_IEN_INIT, &regs->usb_ien);
>> +	writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, &regs->usb_conf);
>> +	writel(USB_CONF_DMULT, &regs->usb_conf);
>> +	writel(USB_CONF_DEVEN, &regs->usb_conf);
>
> If you are enabling interrupts in this patch you should handle them in the ISR.

Frankly, I don't understand why this is a series. It's a single driver
and splitting it into a series just makes it more difficult to review,
actually.

Sure, a single patch will be large, but there's no way to have a
functional driver until all patches are applied, anyway.

-- 
balbi

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-11-18 10:09 ` [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API Pawel Laszczak
@ 2018-11-28 12:22   ` Roger Quadros
  2018-12-01 11:11     ` Pawel Laszczak
  2018-12-10  2:12     ` Peter Chen
  0 siblings, 2 replies; 85+ messages in thread
From: Roger Quadros @ 2018-11-28 12:22 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul, Felipe Balbi



On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch adds implementation callback function defined in
> usb_gadget_ops object.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/gadget.c | 249 ++++++++++++++++++++++++++++++++++++-
>  1 file changed, 247 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
> index 376b68b13d1b..702a05faa664 100644
> --- a/drivers/usb/cdns3/gadget.c
> +++ b/drivers/usb/cdns3/gadget.c
> @@ -17,6 +17,36 @@
>  #include "gadget-export.h"
>  #include "gadget.h"
>  
> +/**
> + * cdns3_handshake - spin reading  until handshake completes or fails
> + * @ptr: address of device controller register to be read
> + * @mask: bits to look at in result of read
> + * @done: value of those bits when handshake succeeds
> + * @usec: timeout in microseconds
> + *
> + * Returns negative errno, or zero on success
> + *
> + * Success happens when the "mask" bits have the specified value (hardware
> + * handshake done). There are two failure modes: "usec" have passed (major
> + * hardware flakeout), or the register reads as all-ones (hardware removed).
> + */
> +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
> +{
> +	u32	result;
> +
> +	do {
> +		result = readl(ptr);
> +		if (result == ~(u32)0)	/* card removed */
> +			return -ENODEV;

Is this applicable to all registers?
What is meant by card removed? We're not connected to host?

how does EP reset behave when there is no USB connection?

> +		result &= mask;
> +		if (result == done)
> +			return 0;
> +		udelay(1);
> +		usec--;
> +	} while (usec > 0);
> +	return -ETIMEDOUT;
> +}
> +
>  /**
>   * cdns3_set_register_bit - set bit in given register.
>   * @ptr: address of device controller register to be read and changed
> @@ -43,6 +73,25 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
>  	writel(ep, &priv_dev->regs->ep_sel);
>  }
>  
> +static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
> +{
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +
> +	if (priv_ep->trb_pool) {
> +		dma_free_coherent(priv_dev->sysdev,
> +				  TRB_RIGN_SIZE,
> +				  priv_ep->trb_pool, priv_ep->trb_pool_dma);
> +		priv_ep->trb_pool = NULL;
> +	}
> +
> +	if (priv_ep->aligned_buff) {
> +		dma_free_coherent(priv_dev->sysdev, CDNS3_UNALIGNED_BUF_SIZE,
> +				  priv_ep->aligned_buff,
> +				  priv_ep->aligned_dma_addr);
> +		priv_ep->aligned_buff = NULL;
> +	}
> +}
> +
>  /**
>   * cdns3_irq_handler - irq line interrupt handler
>   * @cdns: cdns3 instance
> @@ -58,6 +107,114 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>  	return ret;
>  }
>  
> +/* Find correct direction for HW endpoint according to description */
> +static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
> +				   struct cdns3_endpoint *priv_ep)
> +{
> +	return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) ||
> +	       (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc));
> +}
> +
> +static struct cdns3_endpoint *cdns3_find_available_ss_ep(struct cdns3_device *priv_dev,
> +							 struct usb_endpoint_descriptor *desc)

why is this function called ss_ep? This doesn't seem like only for superspeed endpoints.

> +{
> +	struct usb_ep *ep;
> +	struct cdns3_endpoint *priv_ep;
> +
> +	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
> +		unsigned long num;
> +		int ret;
> +		/* ep name pattern likes epXin or epXout */
> +		char c[2] = {ep->name[2], '\0'};
> +
> +		ret = kstrtoul(c, 10, &num);
> +		if (ret)
> +			return ERR_PTR(ret);
> +
> +		priv_ep = ep_to_cdns3_ep(ep);
> +		if (cdns3_ep_dir_is_correct(desc, priv_ep)) {
> +			if (!(priv_ep->flags & EP_USED)) {
> +				priv_ep->num  = num;
> +				priv_ep->flags |= EP_USED;
> +				return priv_ep;
> +			}
> +		}
> +	}
> +	return ERR_PTR(-ENOENT);
> +}
> +
> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
> +					    struct usb_endpoint_descriptor *desc,
> +					    struct usb_ss_ep_comp_descriptor *comp_desc)
> +{
> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> +	struct cdns3_endpoint *priv_ep;
> +	unsigned long flags;
> +
> +	priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
> +	if (IS_ERR(priv_ep)) {
> +		dev_err(&priv_dev->dev, "no available ep\n");
> +		return NULL;
> +	}
> +
> +	dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	priv_ep->endpoint.desc = desc;
> +	priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
> +	priv_ep->type = usb_endpoint_type(desc);
> +
> +	list_add_tail(&priv_ep->ep_match_pending_list,
> +		      &priv_dev->ep_match_list);
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return &priv_ep->endpoint;
> +}

Why do you need a custom match_ep?
doesn't usb_ep_autoconfig suffice?

You can check if EP is claimed or not by checking the ep->claimed flag.

> +
> +/**
> + * cdns3_gadget_get_frame Returns number of actual ITP frame
> + * @gadget: gadget object
> + *
> + * Returns number of actual ITP frame
> + */
> +static int cdns3_gadget_get_frame(struct usb_gadget *gadget)
> +{
> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> +
> +	return readl(&priv_dev->regs->usb_iptn);
> +}
> +
> +static int cdns3_gadget_wakeup(struct usb_gadget *gadget)
> +{
> +	return 0;
> +}
> +
> +static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget,
> +					int is_selfpowered)
> +{
> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	gadget->is_selfpowered = !!is_selfpowered;
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return 0;
> +}
> +
> +static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on)
> +{
> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> +
> +	if (!priv_dev->start_gadget)
> +		return 0;
> +
> +	if (is_on)
> +		writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf);
> +	else
> +		writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
> +
> +	return 0;
> +}
> +
>  static void cdns3_gadget_config(struct cdns3_device *priv_dev)
>  {
>  	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
> @@ -74,6 +231,95 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev)
>  	writel(USB_CONF_DEVEN, &regs->usb_conf);
>  }
>  
> +/**
> + * cdns3_gadget_udc_start Gadget start
> + * @gadget: gadget object
> + * @driver: driver which operates on this gadget
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +static int cdns3_gadget_udc_start(struct usb_gadget *gadget,
> +				  struct usb_gadget_driver *driver)
> +{
> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> +	unsigned long flags;
> +
> +	if (priv_dev->gadget_driver) {
> +		dev_err(&priv_dev->dev, "%s is already bound to %s\n",
> +			priv_dev->gadget.name,
> +			priv_dev->gadget_driver->driver.name);
> +		return -EBUSY;
> +	}

Not sure if this check is required. UDC core should be doing that.

> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	priv_dev->gadget_driver = driver;
> +	if (!priv_dev->start_gadget)
> +		goto unlock;
> +
> +	cdns3_gadget_config(priv_dev);
> +unlock:
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return 0;
> +}
> +
> +/**
> + * cdns3_gadget_udc_stop Stops gadget
> + * @gadget: gadget object
> + *
> + * Returns 0
> + */
> +static int cdns3_gadget_udc_stop(struct usb_gadget *gadget)
> +{
> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> +	struct cdns3_endpoint *priv_ep, *temp_ep;
> +	u32 bEndpointAddress;
> +	struct usb_ep *ep;
> +	int ret = 0;
> +	int i;
> +
> +	priv_dev->gadget_driver = NULL;
> +	list_for_each_entry_safe(priv_ep, temp_ep, &priv_dev->ep_match_list,
> +				 ep_match_pending_list) {
> +		list_del(&priv_ep->ep_match_pending_list);
> +		priv_ep->flags &= ~EP_USED;
> +	}
> +
> +	priv_dev->onchip_mem_allocated_size = 0;
> +	priv_dev->out_mem_is_allocated = 0;
> +	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> +
> +	for (i = 0; i < priv_dev->ep_nums ; i++)
> +		cdns3_free_trb_pool(priv_dev->eps[i]);
> +
> +	if (!priv_dev->start_gadget)
> +		return 0;

This looks tricky. Why do we need this flag?

> +
> +	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
> +		priv_ep = ep_to_cdns3_ep(ep);
> +		bEndpointAddress = priv_ep->num | priv_ep->dir;
> +		cdns3_select_ep(priv_dev, bEndpointAddress);
> +		writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
> +		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
> +				      EP_CMD_EPRST, 0, 100);
> +	}
> +
> +	/* disable interrupt for device */
> +	writel(0, &priv_dev->regs->usb_ien);
> +	writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);

where are you requesting the interrupt? Looks like it should be done in
udc_start() no?

> +
> +	return ret;
> +}

Can we combine cdns3_gadget_udc_start() and cdns3_gadget_udc_start()
with cdns3_gadget_start() and cdns3_gadget_stop() respectively so that
cdns3_gadget_config() and cleanup() is done at one place.

> +
> +static const struct usb_gadget_ops cdns3_gadget_ops = {
> +	.get_frame = cdns3_gadget_get_frame,
> +	.wakeup = cdns3_gadget_wakeup,
> +	.set_selfpowered = cdns3_gadget_set_selfpowered,
> +	.pullup = cdns3_gadget_pullup,
> +	.udc_start = cdns3_gadget_udc_start,
> +	.udc_stop = cdns3_gadget_udc_stop,
> +	.match_ep = cdns3_gadget_match_ep,
> +};
> +
>  /**
>   * cdns3_init_ep Initializes software endpoints of gadget
>   * @cdns3: extended gadget object
> @@ -184,8 +430,7 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
>  	/* fill gadget fields */
>  	priv_dev->gadget.max_speed = USB_SPEED_SUPER;
>  	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> -	//TODO: Add implementation of cdns3_gadget_ops
> -	//priv_dev->gadget.ops = &cdns3_gadget_ops;
> +	priv_dev->gadget.ops = &cdns3_gadget_ops;
>  	priv_dev->gadget.name = "usb-ss-gadget";
>  	priv_dev->gadget.sg_supported = 1;
>  	priv_dev->is_connected = 0;
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 09/15] usb:cdns3: EpX operations part of the API
  2018-11-18 10:09 ` [RFC PATCH v2 09/15] usb:cdns3: EpX " Pawel Laszczak
@ 2018-11-28 12:46   ` Roger Quadros
  2018-12-01 13:30     ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-28 12:46 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul



On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch implements callback functions for non-default endpoints
> defined in usb_ep_ops object.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/ep0.c    |  18 ++
>  drivers/usb/cdns3/gadget.c | 442 ++++++++++++++++++++++++++++++++++++-
>  drivers/usb/cdns3/gadget.h |   3 +
>  3 files changed, 461 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
> index c08d02665f9d..ca1795467155 100644
> --- a/drivers/usb/cdns3/ep0.c
> +++ b/drivers/usb/cdns3/ep0.c
> @@ -23,6 +23,24 @@ static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>  	//TODO: Implements this function
>  }
>  
> +/**
> + * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint
> + * @ep: endpoint object
> + *
> + * Returns 0
> + */
> +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +
> +	dev_dbg(&priv_dev->dev, "Wedge for %s\n", ep->name);
> +	cdns3_gadget_ep_set_halt(ep, 1);
> +	priv_ep->flags |= EP_WEDGE;
> +
> +	return 0;
> +}
> +
>  /**
>   * cdns3_ep0_config - Configures default endpoint
>   * @priv_dev: extended gadget object
> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
> index 702a05faa664..1f2a434486dc 100644
> --- a/drivers/usb/cdns3/gadget.c
> +++ b/drivers/usb/cdns3/gadget.c
> @@ -58,6 +58,19 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
>  	writel(mask, ptr);
>  }
>  
> +/**
> + * cdns3_next_request - returns next request from list
> + * @list: list containing requests
> + *
> + * Returns request or NULL if no requests in list
> + */
> +struct usb_request *cdns3_next_request(struct list_head *list)
> +{
> +	if (list_empty(list))
> +		return NULL;
> +	return list_first_entry(list, struct usb_request, list);
> +}
> +
>  /**
>   * select_ep - selects endpoint
>   * @priv_dev:  extended gadget object
> @@ -73,6 +86,53 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
>  	writel(ep, &priv_dev->regs->ep_sel);
>  }
>  
> +/**
> + * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint
> + * @priv_ep:  endpoint object
> + *
> + * Function will return 0 on success or -ENOMEM on allocation error
> + */
> +static int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep)
> +{
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	struct cdns3_trb *link_trb;
> +
> +	if (!priv_ep->trb_pool) {
> +		priv_ep->trb_pool = dma_zalloc_coherent(priv_dev->sysdev,
> +							TRB_RIGN_SIZE,

TRB_RING_SIZE

> +							&priv_ep->trb_pool_dma,
> +							GFP_DMA);
> +		if (!priv_ep->trb_pool)
> +			return -ENOMEM;
> +	} else {
> +		memset(priv_ep->trb_pool, 0, TRB_RIGN_SIZE);

here too.

> +	}
> +
> +	if (!priv_ep->aligned_buff) {
> +		priv_ep->aligned_buff = dma_alloc_coherent(priv_dev->sysdev,
> +							   CDNS3_UNALIGNED_BUF_SIZE,

CDNS3_ALIGNED_BUF_SIZE

> +							   &priv_ep->aligned_dma_addr,
> +							   GFP_DMA);
> +		if (!priv_ep->aligned_buff) {
> +			dma_free_coherent(priv_dev->sysdev,
> +					  TRB_RIGN_SIZE,
> +					  priv_ep->trb_pool,
> +					  priv_ep->trb_pool_dma);
> +			priv_ep->trb_pool = NULL;
> +
> +			return -ENOMEM;
> +		}
> +	}
> +
> +	/* Initialize the last TRB as Link TRB */
> +	link_trb = (priv_ep->trb_pool + TRBS_PER_SEGMENT - 1);
> +	link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma);
> +	link_trb->control = TRB_CYCLE | TRB_TYPE(TRB_LINK) |
> +			    TRB_CHAIN | TRB_TOGGLE;
> +
> +	return 0;
> +}
> +
>  static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
>  {
>  	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> @@ -92,6 +152,73 @@ static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
>  	}
>  }
>  
> +/**
> + * cdns3_data_flush - flush data at onchip buffer
> + * @priv_ep: endpoint object
> + *
> + * Endpoint must be selected before call to this function
> + *
> + * Returns zero on success or negative value on failure
> + */
> +static int cdns3_data_flush(struct cdns3_endpoint *priv_ep)
> +{
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +
> +	writel(EP_CMD_DFLUSH, &priv_dev->regs->ep_cmd);
> +
> +	/* wait for DFLUSH cleared */
> +	return cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 100);
> +}
> +
> +/**
> + * cdns3_ep_stall_flush - Stalls and flushes selected endpoint
> + * @priv_ep: endpoint object
> + *
> + * Endpoint must be selected before call to this function
> + */
> +static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
> +{
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +
> +	writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL,
> +	       &priv_dev->regs->ep_cmd);
> +
> +	/* wait for DFLUSH cleared */
> +	cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 100);
> +	priv_ep->flags |= EP_STALL;
> +}
> +
> +/**
> + * cdns3_gadget_giveback - call struct usb_request's ->complete callback
> + * @priv_ep: The endpoint to whom the request belongs to
> + * @priv_req: The request we're giving back
> + * @status: completion code for the request
> + *
> + * Must be called with controller's lock held and interrupts disabled. This
> + * function will unmap @req and call its ->complete() callback to notify upper
> + * layers that it has completed.
> + */
> +void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
> +			   struct cdns3_request *priv_req,
> +			   int status)
> +{
> +	//TODO: Implements this function.
> +}
> +
> +/**
> + * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware
> + * @priv_ep: endpoint object
> + *
> + * Returns zero on success or negative value on failure
> + */
> +int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
> +			  struct usb_request *request)
> +{
> +	//TODO: Implements this function.
> +
> +	return 0;
> +}
> +
>  /**
>   * cdns3_irq_handler - irq line interrupt handler
>   * @cdns: cdns3 instance
> @@ -170,6 +297,318 @@ static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>  	return &priv_ep->endpoint;
>  }
>  
> +/**
> + * cdns3_gadget_ep_enable Enable endpoint
> + * @ep: endpoint object
> + * @desc: endpoint descriptor
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +static int cdns3_gadget_ep_enable(struct usb_ep *ep,
> +				  const struct usb_endpoint_descriptor *desc)
> +{
> +	struct cdns3_endpoint *priv_ep;
> +	struct cdns3_device *priv_dev;
> +	unsigned long flags;
> +	int ret;
> +	u32 reg;
> +
> +	priv_ep = ep_to_cdns3_ep(ep);
> +	priv_dev = priv_ep->cdns3_dev;
> +
> +	if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) {
> +		dev_err(&priv_dev->dev, "usbss: invalid parameters\n");

dev_dbg()?

Gadget driver will be more verbose.

> +		return -EINVAL;
> +	}
> +
> +	if (!desc->wMaxPacketSize) {
> +		dev_err(&priv_dev->dev, "usbss: missing wMaxPacketSize\n");
> +		return -EINVAL;
> +	}
> +
> +	if (dev_WARN_ONCE(&priv_dev->dev, priv_ep->flags & EP_ENABLED,
> +			  "%s is already enabled\n", priv_ep->name))
> +		return 0;
> +
> +	ret = cdns3_allocate_trb_pool(priv_ep);
> +	if (ret)
> +		return ret;

Why not allocate the TRB pool once for all endpoints at gadget init?

you don't seem to be calling cdns3_allocate_trb_pool() in cdns3_gadget_ep_disable().

> +
> +	dev_dbg(&priv_dev->dev, "Enabling endpoint: %s\n", ep->name);
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	cdns3_select_ep(priv_dev, desc->bEndpointAddress);
> +	writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
> +
> +	ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
> +			      EP_CMD_CSTALL | EP_CMD_EPRST, 0, 100);
> +
> +	cdns3_set_register_bit(&priv_dev->regs->ep_cfg, EP_CFG_ENABLE);
> +
> +	ep->desc = desc;
> +	priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALL);
> +	priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR;
> +	priv_ep->enqueue = 0;
> +	priv_ep->dequeue = 0;
> +	reg = readl(&priv_dev->regs->ep_sts);
> +	priv_ep->pcs = !!EP_STS_CCS(reg);
> +	priv_ep->ccs = !!EP_STS_CCS(reg);
> +	/* one TRB is reserved for link TRB used in DMULT mode*/
> +	priv_ep->free_trbs = TRBS_PER_SEGMENT - 1;
> +
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return 0;
> +}
> +
> +/**
> + * cdns3_gadget_ep_disable Disable endpoint
> + * @ep: endpoint object
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +static int cdns3_gadget_ep_disable(struct usb_ep *ep)
> +{
> +	struct cdns3_endpoint *priv_ep;
> +	struct cdns3_device *priv_dev;
> +	unsigned long flags;
> +	int ret = 0;
> +	struct usb_request *request;
> +	u32 ep_cfg;
> +
> +	if (!ep) {
> +		pr_debug("usbss: invalid parameters\n");
> +		return -EINVAL;
> +	}
> +
> +	priv_ep = ep_to_cdns3_ep(ep);
> +	priv_dev = priv_ep->cdns3_dev;
> +
> +	if (dev_WARN_ONCE(&priv_dev->dev, !(priv_ep->flags & EP_ENABLED),
> +			  "%s is already disabled\n", priv_ep->name))
> +		return 0;
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	if (!priv_dev->start_gadget) {
> +		dev_dbg(&priv_dev->dev,
> +			"Disabling endpoint at disconnection: %s\n", ep->name);

This flag is looking very tricky.
What do you mean by "disabling at disconnection"?

> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
> +		return 0;

EP is not yet disabled and we're returning 0. This will cause an unbalance.
I'd avoid that flag altogether.

> +	}
> +
> +	dev_dbg(&priv_dev->dev, "Disabling endpoint: %s\n", ep->name);
> +
> +	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
> +	ret = cdns3_data_flush(priv_ep);
> +	while (!list_empty(&priv_ep->request_list)) {
> +		request = cdns3_next_request(&priv_ep->request_list);
> +
> +		cdns3_gadget_giveback(priv_ep, to_cdns3_request(request),
> +				      -ESHUTDOWN);
> +	}
> +
> +	ep_cfg = readl(&priv_dev->regs->ep_cfg);
> +	ep_cfg &= ~EP_CFG_ENABLE;
> +	writel(ep_cfg, &priv_dev->regs->ep_cfg);
> +	ep->desc = NULL;
> +	priv_ep->flags &= ~EP_ENABLED;
> +
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +
> +	return ret;
> +}
> +
> +/**
> + * cdns3_gadget_ep_alloc_request Allocates request
> + * @ep: endpoint object associated with request
> + * @gfp_flags: gfp flags
> + *
> + * Returns allocated request address, NULL on allocation error
> + */
> +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
> +						  gfp_t gfp_flags)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_request *priv_req;
> +
> +	priv_req = kzalloc(sizeof(*priv_req), gfp_flags);
> +	if (!priv_req)
> +		return NULL;
> +
> +	priv_req->priv_ep = priv_ep;
> +
> +	return &priv_req->request;
> +}
> +
> +/**
> + * cdns3_gadget_ep_free_request Free memory occupied by request
> + * @ep: endpoint object associated with request
> + * @request: request to free memory
> + */
> +void cdns3_gadget_ep_free_request(struct usb_ep *ep,
> +				  struct usb_request *request)
> +{
> +	struct cdns3_request *priv_req = to_cdns3_request(request);
> +
> +	kfree(priv_req);
> +}
> +
> +/**
> + * cdns3_gadget_ep_queue Transfer data on endpoint
> + * @ep: endpoint object
> + * @request: request object
> + * @gfp_flags: gfp flags
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +static int __cdns3_gadget_ep_queue(struct usb_ep *ep,
> +				   struct usb_request *request,
> +				   gfp_t gfp_flags)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	int ret = 0;
> +
> +	request->actual = 0;
> +	request->status = -EINPROGRESS;
> +
> +	dev_dbg(&priv_dev->dev, "Queuing to endpoint: %s\n", priv_ep->name);
> +
> +	ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
> +					    usb_endpoint_dir_in(ep->desc));
> +
> +	if (ret)
> +		return ret;
> +
> +	if (!cdns3_ep_run_transfer(priv_ep, request))
> +		list_add_tail(&request->list, &priv_ep->request_list);

how about catching the return value if cdns3_ep_run_transfer() fails?

> +
> +	return ret;
> +}
> +
> +static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
> +				 gfp_t gfp_flags)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	struct usb_request *zlp_request;
> +	unsigned long flags;
> +	int ret;
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags);
> +
> +	if (ret == 0 && request->zero && request->length &&
> +	    (request->length % ep->maxpacket == 0)) {
> +		zlp_request = cdns3_gadget_ep_alloc_request(ep, GFP_ATOMIC);
> +		zlp_request->buf = priv_dev->zlp_buf;
> +		zlp_request->length = 0;
> +
> +		dev_dbg(&priv_dev->dev, "Queuing ZLP for endpoint: %s\n",
> +			priv_ep->name);
> +		ret = __cdns3_gadget_ep_queue(ep, zlp_request, gfp_flags);

Who is going to free this zlp_request?

> +	}
> +
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return ret;
> +}
> +
> +/**
> + * cdns3_gadget_ep_dequeue Remove request from transfer queue
> + * @ep: endpoint object associated with request
> + * @request: request object
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +int cdns3_gadget_ep_dequeue(struct usb_ep *ep,
> +			    struct usb_request *request)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	struct usb_request *req, *req_temp;
> +	unsigned long flags;
> +	int ret = 0;
> +
> +	if (!ep || !request || !ep->desc)
> +		return -EINVAL;
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	dev_dbg(&priv_dev->dev, "Dequeue from %s\n", ep->name);
> +
> +	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
> +	if (priv_dev->start_gadget)
> +		ret = cdns3_data_flush(priv_ep);
> +
> +	list_for_each_entry_safe(req, req_temp, &priv_ep->request_list, list) {
> +		if (request == req) {
> +			cdns3_gadget_giveback(priv_ep,
> +					      to_cdns3_request(request),
> +					      -ECONNRESET);
> +			break;
> +		}
> +	}
> +
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +	return ret;
> +}
> +
> +/**
> + * cdns3_gadget_ep_set_halt Sets/clears stall on selected endpoint
> + * @ep: endpoint object to set/clear stall on
> + * @value: 1 for set stall, 0 for clear stall
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	unsigned long flags;
> +	int ret = 0;
> +
> +	if (!(priv_ep->flags & EP_ENABLED))
> +		return -EPERM;
> +
> +	/* if actual transfer is pending defer setting stall on this endpoint */
> +	if ((priv_ep->flags & EP_PENDING_REQUEST) && value) {
> +		priv_ep->flags |= EP_STALL;
> +		return 0;
> +	}
> +
> +	dev_dbg(&priv_dev->dev, "Halt endpoint %s\n", priv_ep->name);
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);

How about grabbing the lock before checking for priv->ep->flags?

> +
> +	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
> +	if (value) {
> +		cdns3_ep_stall_flush(priv_ep);
> +	} else {
> +		priv_ep->flags &= ~EP_WEDGE;
> +		writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
> +
> +		/* wait for EPRST cleared */
> +		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
> +				      EP_CMD_EPRST, 0, 100);

if there was an error we shouldn't be clearing the EP_STALL right
and leave the pending flag?

> +		priv_ep->flags &= ~EP_STALL;
> +	}
> +
> +	priv_ep->flags &= ~EP_PENDING_REQUEST;
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +
> +	return ret;
> +}
> +
> +extern const struct usb_ep_ops cdns3_gadget_ep0_ops;
> +
> +static const struct usb_ep_ops cdns3_gadget_ep_ops = {
> +	.enable = cdns3_gadget_ep_enable,
> +	.disable = cdns3_gadget_ep_disable,
> +	.alloc_request = cdns3_gadget_ep_alloc_request,
> +	.free_request = cdns3_gadget_ep_free_request,
> +	.queue = cdns3_gadget_ep_queue,
> +	.dequeue = cdns3_gadget_ep_dequeue,
> +	.set_halt = cdns3_gadget_ep_set_halt,
> +	.set_wedge = cdns3_gadget_ep_set_wedge,
> +};
> +
>  /**
>   * cdns3_gadget_get_frame Returns number of actual ITP frame
>   * @gadget: gadget object
> @@ -365,8 +804,7 @@ static int cdns3_init_ep(struct cdns3_device *priv_dev)
>  		usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
>  					   ENDPOINT_MAX_PACKET_LIMIT);
>  		priv_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;
> -		//TODO: Add implementation of cdns3_gadget_ep_ops
> -		//priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
> +		priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
>  		if (ep_dir)
>  			priv_ep->endpoint.caps.dir_in = 1;
>  		else
> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
> index 3b0d4d2e4831..a4be288b34cb 100644
> --- a/drivers/usb/cdns3/gadget.h
> +++ b/drivers/usb/cdns3/gadget.h
> @@ -1072,4 +1072,7 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
> +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
> +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
> +
>  #endif /* __LINUX_CDNS3_GADGET */
> 

cheers,
-roger
-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 10/15] usb:cdns3: Ep0 operations part of the API
  2018-11-18 10:09 ` [RFC PATCH v2 10/15] usb:cdns3: Ep0 " Pawel Laszczak
@ 2018-11-28 14:31   ` Roger Quadros
  2018-12-02 10:34     ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-28 14:31 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul



On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch implements related to default endpoint callback functions
> defined in usb_ep_ops object
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/ep0.c    | 191 ++++++++++++++++++++++++++++++++++++-
>  drivers/usb/cdns3/gadget.c |   8 ++
>  drivers/usb/cdns3/gadget.h |  10 ++
>  3 files changed, 207 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
> index ca1795467155..d05169e73631 100644
> --- a/drivers/usb/cdns3/ep0.c
> +++ b/drivers/usb/cdns3/ep0.c
> @@ -18,11 +18,185 @@ static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
>  	.bmAttributes =	USB_ENDPOINT_XFER_CONTROL,
>  };
>  
> +/**
> + * cdns3_ep0_run_transfer - Do transfer on default endpoint hardware
> + * @priv_dev: extended gadget object
> + * @dma_addr: physical address where data is/will be stored
> + * @length: data length
> + * @erdy: set it to 1 when ERDY packet should be sent -
> + *        exit from flow control state
> + */
> +static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
> +				   dma_addr_t dma_addr,
> +				   unsigned int length, int erdy)
> +{
> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
> +
> +	priv_dev->trb_ep0->buffer = TRB_BUFFER(dma_addr);
> +	priv_dev->trb_ep0->length = TRB_LEN(length);
> +	priv_dev->trb_ep0->control = TRB_CYCLE | TRB_IOC | TRB_TYPE(TRB_NORMAL);
> +
> +	cdns3_select_ep(priv_dev,
> +			priv_dev->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT);
> +
> +	writel(EP_STS_TRBERR, &regs->ep_sts);
> +	writel(EP_TRADDR_TRADDR(priv_dev->trb_ep0_dma), &regs->ep_traddr);
> +
> +	dev_dbg(&priv_dev->dev, "//Ding Dong ep0%s\n",
> +		priv_dev->ep0_data_dir ? "IN" : "OUT");
> +
> +	/* TRB should be prepared before starting transfer */
> +	writel(EP_CMD_DRDY, &regs->ep_cmd);
> +
> +	if (erdy)
> +		writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
> +}
> +
>  static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>  {
>  	//TODO: Implements this function
>  }
>  
> +static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)

Is this going to be used only for ep0?
If yes then let's have ep0 in the name.
If not then this function should be in gadget.c

> +{
> +	struct cdns3_endpoint *priv_ep;
> +	struct usb_request *request;
> +	struct usb_ep *ep;
> +	int result = 0;
> +
> +	if (priv_dev->hw_configured_flag)
> +		return;
> +
> +	writel(USB_CONF_CFGSET, &priv_dev->regs->usb_conf);
> +	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
> +
> +	cdns3_set_register_bit(&priv_dev->regs->usb_conf,
> +			       USB_CONF_U1EN | USB_CONF_U2EN);

Shouldn't U1/U2 be enabled only if the USB_DEVICE_U1_ENABLE/USB_DEVICE_U2_ENABLE
device request was received?

> +
> +	/* wait until configuration set */
> +	result = cdns3_handshake(&priv_dev->regs->usb_sts,
> +				 USB_STS_CFGSTS_MASK, 1, 100);
> +
> +	priv_dev->hw_configured_flag = 1;
> +	cdns3_enable_l1(priv_dev, 1);
> +
> +	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
> +		if (ep->enabled) {
> +			priv_ep = ep_to_cdns3_ep(ep);
> +			request = cdns3_next_request(&priv_ep->request_list);
> +			if (request)
> +				cdns3_ep_run_transfer(priv_ep, request);

why are you starting transfers in a function that is supposed to set hw configuration only.

> +		}
> +	}
> +}
> +
> +/**
> + * cdns3_gadget_ep0_enable
> + * Function shouldn't be called by gadget driver,
> + * endpoint 0 is allways active
> + */
> +static int cdns3_gadget_ep0_enable(struct usb_ep *ep,
> +				   const struct usb_endpoint_descriptor *desc)
> +{
> +	return -EINVAL;
> +}
> +
> +/**
> + * cdns3_gadget_ep0_disable
> + * Function shouldn't be called by gadget driver,
> + * endpoint 0 is allways active
> + */
> +static int cdns3_gadget_ep0_disable(struct usb_ep *ep)
> +{
> +	return -EINVAL;
> +}
> +
> +/**
> + * cdns3_gadget_ep0_set_halt
> + * @ep: pointer to endpoint zero object
> + * @value: 1 for set stall, 0 for clear stall
> + *
> + * Returns 0
> + */
> +static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value)
> +{
> +	/* TODO */
> +	return 0;
> +}
> +
> +/**
> + * cdns3_gadget_ep0_queue Transfer data on endpoint zero
> + * @ep: pointer to endpoint zero object
> + * @request: pointer to request object
> + * @gfp_flags: gfp flags
> + *
> + * Returns 0 on success, error code elsewhere
> + */
> +static int cdns3_gadget_ep0_queue(struct usb_ep *ep,
> +				  struct usb_request *request,
> +				  gfp_t gfp_flags)
> +{
> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	unsigned long flags;
> +	int erdy_sent = 0;
> +	int ret = 0;
> +
> +	dev_dbg(&priv_dev->dev, "Queue to Ep0%s L: %d\n",
> +		priv_dev->ep0_data_dir ? "IN" : "OUT",
> +		request->length);
> +
> +	/* send STATUS stage */
> +	if (request->length == 0 && request->zero == 0) {

Is this check sufficient to know STATUS stage?
It might be normal for vendor specific control request to have
request->length = 0 and request->zero = 0.


> +		spin_lock_irqsave(&priv_dev->lock, flags);
> +		cdns3_select_ep(priv_dev, 0x00);
> +
> +		erdy_sent = !priv_dev->hw_configured_flag;
> +		cdns3_set_hw_configuration(priv_dev);

What if we're still busy with DATA stage of previous ep0_queue?

if (!list_empty(&priv_ep->request_list)) should be done as the first thing in this function.

> +
> +		if (!erdy_sent)
> +			writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL,
> +			       &priv_dev->regs->ep_cmd);
> +
> +		cdns3_prepare_setup_packet(priv_dev);
> +		request->actual = 0;
> +		priv_dev->status_completion_no_call = true;
> +		priv_dev->pending_status_request = request;
> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
> +
> +		/*
> +		 * Since there is no completion interrupt for status stage,
> +		 * it needs to call ->completion in software after
> +		 * ep0_queue is back.
> +		 */
> +		queue_work(system_freezable_wq, &priv_dev->pending_status_wq);
> +		return 0;
> +	}
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	if (!list_empty(&priv_ep->request_list)) {
> +		dev_err(&priv_dev->dev,
> +			"can't handle multiple requests for ep0\n");
> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
> +		return -EOPNOTSUPP;

-EBUSY?

> +	}
> +
> +	ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
> +					    priv_dev->ep0_data_dir);
> +	if (ret) {
> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
> +		dev_err(&priv_dev->dev, "failed to map request\n");
> +		return -EINVAL;
> +	}
> +
> +	priv_dev->ep0_request = request;
> +	list_add_tail(&request->list, &priv_ep->request_list);
> +	cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1);
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
> +
> +	return ret;
> +}
> +
>  /**
>   * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint
>   * @ep: endpoint object
> @@ -41,6 +215,17 @@ int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
>  	return 0;
>  }
>  
> +const struct usb_ep_ops cdns3_gadget_ep0_ops = {
> +	.enable = cdns3_gadget_ep0_enable,
> +	.disable = cdns3_gadget_ep0_disable,
> +	.alloc_request = cdns3_gadget_ep_alloc_request,
> +	.free_request = cdns3_gadget_ep_free_request,
> +	.queue = cdns3_gadget_ep0_queue,
> +	.dequeue = cdns3_gadget_ep_dequeue,
> +	.set_halt = cdns3_gadget_ep0_set_halt,
> +	.set_wedge = cdns3_gadget_ep_set_wedge,
> +};
> +
>  /**
>   * cdns3_ep0_config - Configures default endpoint
>   * @priv_dev: extended gadget object
> @@ -62,6 +247,9 @@ void cdns3_ep0_config(struct cdns3_device *priv_dev)
>  		priv_dev->ep0_request = NULL;
>  	}
>  
> +	priv_dev->u1_allowed = 0;
> +	priv_dev->u2_allowed = 0;
> +

where do you set these?

>  	priv_dev->gadget.ep0->maxpacket = max_packet_size;
>  	cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
>  
> @@ -106,8 +294,7 @@ int cdns3_init_ep0(struct cdns3_device *priv_dev)
>  	sprintf(ep0->name, "ep0");
>  
>  	/* fill linux fields */
> -	//TODO: implements cdns3_gadget_ep0_ops object
> -	//ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
> +	ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
>  	ep0->endpoint.maxburst = 1;
>  	usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
>  	ep0->endpoint.address = 0;
> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
> index 1f2a434486dc..c965da16c0c8 100644
> --- a/drivers/usb/cdns3/gadget.c
> +++ b/drivers/usb/cdns3/gadget.c
> @@ -188,6 +188,14 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
>  	priv_ep->flags |= EP_STALL;
>  }
>  

> +void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)

Some comment might help as to what L1 is.

> +{
> +	if (enable)
> +		writel(USB_CONF_L1EN, &priv_dev->regs->usb_conf);
> +	else
> +		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
> +}
> +
>  /**
>   * cdns3_gadget_giveback - call struct usb_request's ->complete callback
>   * @priv_ep: The endpoint to whom the request belongs to
> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
> index a4be288b34cb..224f6b830bc9 100644
> --- a/drivers/usb/cdns3/gadget.h
> +++ b/drivers/usb/cdns3/gadget.h
> @@ -1068,11 +1068,21 @@ struct cdns3_device {
>  	struct usb_request		*pending_status_request;
>  };
>  
> +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
> +void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
> +struct usb_request *cdns3_next_request(struct list_head *list);
> +int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
> +			  struct usb_request *request);
>  int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
>  int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
> +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
> +						  gfp_t gfp_flags);
> +void cdns3_gadget_ep_free_request(struct usb_ep *ep,
> +				  struct usb_request *request);
> +int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request);
>  
>  #endif /* __LINUX_CDNS3_GADGET */
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality.
  2018-11-18 10:09 ` [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality Pawel Laszczak
@ 2018-11-28 14:54   ` Roger Quadros
  2018-12-02 11:49     ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-28 14:54 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul



On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch adds set of generic functions used for handling interrupts
> generated by controller. Interrupt related functions are divided
> into three groups. The first is related to ep0 and is placed in ep0.c.
> The second is responsible for non-default endpoints and is
> implemented in gadget.c file. The last group is not related to
> endpoints interrupts and is placed in gadget.c.
> All groups have common entry point in cdns3_irq_handler_thread function.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/ep0.c    |  63 +++++++++++
>  drivers/usb/cdns3/gadget.c | 224 ++++++++++++++++++++++++++++++++++++-
>  drivers/usb/cdns3/gadget.h |   1 +
>  3 files changed, 287 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
> index d05169e73631..eb92fd234bd7 100644
> --- a/drivers/usb/cdns3/ep0.c
> +++ b/drivers/usb/cdns3/ep0.c
> @@ -90,6 +90,69 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>  	}
>  }
>  
> +static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
> +{
> +	//TODO: Implements this function
> +}
> +
> +/**
> + * cdns3_ep0_setup_phase - Handling setup USB requests
> + * @priv_dev: extended gadget object
> + */
> +static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
> +{
> +	//TODO: Implements this function.
> +}
> +
> +static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
> +{
> +	//TODO: Implements this function
> +}
> +
> +/**
> + * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0
> + * @priv_dev: extended gadget object
> + * @dir: 1 for IN direction, 0 for OUT direction
> + */
> +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir)
> +{
> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
> +	u32 ep_sts_reg;
> +
> +	cdns3_select_ep(priv_dev, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT));
> +	ep_sts_reg = readl(&regs->ep_sts);
> +
> +	__pending_setup_status_handler(priv_dev);
> +
> +	if ((ep_sts_reg & EP_STS_SETUP) && dir == 0) {
> +		struct usb_ctrlrequest *setup = priv_dev->setup;
> +
> +		writel(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);

instead you can just clear all events at the end of this function by
	writel(ep_sts_reg, &regs->ep_sts);
> +
> +		priv_dev->ep0_data_dir = setup->bRequestType & USB_DIR_IN;
> +		cdns3_ep0_setup_phase(priv_dev);
> +		ep_sts_reg &= ~(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP);

Not required.

> +	}
> +
> +	if (ep_sts_reg & EP_STS_TRBERR)
> +		writel(EP_STS_TRBERR, &priv_dev->regs->ep_sts);

Can be omitted.
> +
> +	if (ep_sts_reg & EP_STS_DESCMIS) {
> +		writel(EP_STS_DESCMIS, &priv_dev->regs->ep_sts);

This as well.
> +
> +		if (dir == 0 && !priv_dev->setup_pending) {
> +			priv_dev->ep0_data_dir = 0;
> +			cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
> +					       8, 0);
> +		}
> +	}
> +
> +	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
> +		writel(EP_STS_IOC, &priv_dev->regs->ep_sts);

this write can be omitted.
> +		cdns3_transfer_completed(priv_dev);
> +	}

here you can do
	writel(ep_sts_reg, &regs->ep_sts);
> +}
> +
>  /**
>   * cdns3_gadget_ep0_enable
>   * Function shouldn't be called by gadget driver,
> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
> index c965da16c0c8..309202474e57 100644
> --- a/drivers/usb/cdns3/gadget.c
> +++ b/drivers/usb/cdns3/gadget.c
> @@ -58,6 +58,18 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
>  	writel(mask, ptr);
>  }
>  
> +/**
> + * cdns3_ep_reg_pos_to_index - Macro converts bit position of ep_ists register
> + * to index of endpoint object in cdns3_device.eps[] container
> + * @i: bit position of endpoint for which endpoint object is required
> + *
> + * Remember that endpoint container doesn't contain default endpoint
> + */
> +static u8 cdns3_ep_reg_pos_to_index(int i)
> +{
> +	return ((i / 16) + (((i % 16) - 2) * 2));
> +}
> +
>  /**
>   * cdns3_next_request - returns next request from list
>   * @list: list containing requests
> @@ -188,6 +200,21 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
>  	priv_ep->flags |= EP_STALL;
>  }
>  
> +/**
> + * cdns3_gadget_unconfig - reset device configuration
> + * @priv_dev: extended gadget object
> + */
> +void cdns3_gadget_unconfig(struct cdns3_device *priv_dev)
> +{
> +	/* RESET CONFIGURATION */
> +	writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf);
> +
> +	cdns3_enable_l1(priv_dev, 0);
> +	priv_dev->hw_configured_flag = 0;
> +	priv_dev->onchip_mem_allocated_size = 0;
> +	priv_dev->out_mem_is_allocated = 0;
> +}
> +
>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>  {
>  	if (enable)
> @@ -196,6 +223,23 @@ void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>  		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
>  }
>  
> +static enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev)
> +{
> +	u32 reg;
> +
> +	reg = readl(&priv_dev->regs->usb_sts);
> +
> +	if (DEV_SUPERSPEED(reg))
> +		return USB_SPEED_SUPER;
> +	else if (DEV_HIGHSPEED(reg))
> +		return USB_SPEED_HIGH;
> +	else if (DEV_FULLSPEED(reg))
> +		return USB_SPEED_FULL;
> +	else if (DEV_LOWSPEED(reg))
> +		return USB_SPEED_LOW;
> +	return USB_SPEED_UNKNOWN;
> +}
> +
>  /**
>   * cdns3_gadget_giveback - call struct usb_request's ->complete callback
>   * @priv_ep: The endpoint to whom the request belongs to
> @@ -221,12 +265,136 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
>   */
>  int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>  			  struct usb_request *request)
> +{
> +	return 0;
> +}
> +
> +static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
> +				     struct cdns3_endpoint *priv_ep)
>  {
>  	//TODO: Implements this function.
> +}
> +
> +/**
> + * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint
> + * @priv_ep: endpoint object
> + *
> + * Returns 0
> + */
> +static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep)
> +{
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	struct cdns3_usb_regs __iomem *regs;
> +	u32 ep_sts_reg;
> +
> +	regs = priv_dev->regs;
> +
> +	cdns3_select_ep(priv_dev, priv_ep->endpoint.address);
> +	ep_sts_reg = readl(&regs->ep_sts);
> +
> +	if (ep_sts_reg & EP_STS_TRBERR)
> +		writel(EP_STS_TRBERR, &regs->ep_sts);
> +
> +	if (ep_sts_reg & EP_STS_ISOERR)
> +		writel(EP_STS_ISOERR, &regs->ep_sts);
> +
> +	if (ep_sts_reg & EP_STS_OUTSMM)
> +		writel(EP_STS_OUTSMM, &regs->ep_sts);
> +
> +	if (ep_sts_reg & EP_STS_NRDY)
> +		writel(EP_STS_NRDY, &regs->ep_sts);

Why check for each bit when you are not doing anything. Instead at the end
you could just do
	writel(ep_sts_reg, &regs->ep_sts)
to clear all pending events.

> +
> +	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
> +		writel(EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
> +		cdns3_transfer_completed(priv_dev, priv_ep);
> +	}
> +
> +	if (ep_sts_reg & EP_STS_DESCMIS)
> +		writel(EP_STS_DESCMIS, &regs->ep_sts);
>  
>  	return 0;
>  }
>  
> +/**
> + * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device
> + * @priv_dev: extended gadget object
> + * @usb_ists: bitmap representation of device's reported interrupts
> + * (usb_ists register value)
> + */
> +static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev,
> +					      u32 usb_ists)
> +{
> +	struct cdns3_usb_regs __iomem *regs;
> +	int speed = 0;
> +
> +	regs = priv_dev->regs;
> +
> +	/* Connection detected */
> +	if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) {
> +		writel(USB_ISTS_CON2I | USB_ISTS_CONI, &regs->usb_ists);
> +		speed = cdns3_get_speed(priv_dev);
> +
> +		dev_dbg(&priv_dev->dev, "Connection detected at speed: %s %d\n",
> +			usb_speed_string(speed), speed);
> +
> +		priv_dev->gadget.speed = speed;
> +		priv_dev->is_connected = 1;
> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED);
> +		cdns3_ep0_config(priv_dev);
> +	}
> +
> +	/* SS Disconnection detected */
> +	if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) {
> +		dev_dbg(&priv_dev->dev, "Disconnection detected\n");
> +
> +		writel(USB_ISTS_DIS2I | USB_ISTS_DISI, &regs->usb_ists);
> +		if (priv_dev->gadget_driver &&
> +		    priv_dev->gadget_driver->disconnect) {
> +			spin_unlock(&priv_dev->lock);
> +			priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
> +			spin_lock(&priv_dev->lock);
> +		}
> +		priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED);
> +		priv_dev->is_connected = 0;
> +		cdns3_gadget_unconfig(priv_dev);
> +	}

What about non Super-Speed disconnects?

> +
> +	if (usb_ists & USB_ISTS_L2ENTI) {
> +		dev_dbg(&priv_dev->dev, "Device suspended\n");
> +		writel(USB_ISTS_L2ENTI, &regs->usb_ists);
> +	}
> +
> +	/* Exit from standby mode on L2 exit (Suspend in HS/FS or SS) */
> +	if (usb_ists & USB_ISTS_L2EXTI) {
> +		dev_dbg(&priv_dev->dev, "[Interrupt] L2 exit detected\n");
> +		writel(USB_ISTS_L2EXTI, &regs->usb_ists);
> +	}
> +
> +	/* Exit from standby mode on U3 exit (Suspend in HS/FS or SS). */
> +	if (usb_ists & USB_ISTS_U3EXTI) {
> +		dev_dbg(&priv_dev->dev, "U3 exit detected\n");
> +		writel(USB_ISTS_U3EXTI, &regs->usb_ists);
> +	}
> +
> +	/* resets cases */
> +	if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) {
> +		writel(USB_ISTS_U2RESI | USB_ISTS_UWRESI | USB_ISTS_UHRESI,
> +		       &regs->usb_ists);
> +
> +		/*read again to check the actuall speed*/
> +		speed = cdns3_get_speed(priv_dev);
> +
> +		dev_dbg(&priv_dev->dev, "Reset detected at speed: %s %d\n",
> +			usb_speed_string(speed), speed);
> +
> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_DEFAULT);
> +		priv_dev->gadget.speed = speed;
> +		cdns3_gadget_unconfig(priv_dev);
> +		cdns3_ep0_config(priv_dev);
> +	}
> +}
> +
>  /**
>   * cdns3_irq_handler - irq line interrupt handler
>   * @cdns: cdns3 instance
> @@ -237,8 +405,62 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>   */
>  static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>  {
> +	struct cdns3_device *priv_dev;
>  	irqreturn_t ret = IRQ_NONE;
> -	//TODO: implements this function
> +	unsigned long flags;
> +	u32 reg;
> +
> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +
> +	/* check USB device interrupt */
> +	reg = readl(&priv_dev->regs->usb_ists);
> +	if (reg) {
> +		dev_dbg(&priv_dev->dev, "IRQ: usb_ists: %08X\n", reg);

dev_dbg will be terribly slow to be useful. Tracepoints?

> +		cdns3_check_usb_interrupt_proceed(priv_dev, reg);
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	/* check endpoint interrupt */
> +	reg = readl(&priv_dev->regs->ep_ists);
> +	if (reg != 0) {
> +		dev_dbg(&priv_dev->dev, "IRQ ep_ists: %08X\n", reg);
> +	} else {
> +		if (USB_STS_CFGSTS(readl(&priv_dev->regs->usb_sts)))
> +			ret = IRQ_HANDLED;

Why is this done. We don't seem to be handling anything here.
Don't we need to clear the usb_sts?

> +		goto irqend;
> +	}
> +
> +	/* handle default endpoint OUT */
> +	if (reg & EP_ISTS_EP_OUT0) {
> +		cdns3_check_ep0_interrupt_proceed(priv_dev, 0);
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	/* handle default endpoint IN */
> +	if (reg & EP_ISTS_EP_IN0) {
> +		cdns3_check_ep0_interrupt_proceed(priv_dev, 1);
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	/* check if interrupt from non default endpoint, if no exit */
> +	reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0);
> +	if (!reg)
> +		goto irqend;
> +
> +	do {
> +		unsigned int bit_pos = ffs(reg);
> +		u32 bit_mask = 1 << (bit_pos - 1);
> +		int index;
> +
> +		index = cdns3_ep_reg_pos_to_index(bit_pos);
> +		cdns3_check_ep_interrupt_proceed(priv_dev->eps[index]);
> +		reg &= ~bit_mask;
> +		ret = IRQ_HANDLED;
> +	} while (reg);
> +
> +irqend:
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>  	return ret;
>  }
>  
> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
> index 224f6b830bc9..8c2f363f9340 100644
> --- a/drivers/usb/cdns3/gadget.h
> +++ b/drivers/usb/cdns3/gadget.h
> @@ -1072,6 +1072,7 @@ int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
> +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
>  struct usb_request *cdns3_next_request(struct list_head *list);
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function.
  2018-11-18 10:09 ` [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function Pawel Laszczak
@ 2018-11-28 15:50   ` Roger Quadros
  2018-12-02 16:39     ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Roger Quadros @ 2018-11-28 15:50 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, peter.chen, pjez, kurahul



On 18/11/18 12:09, Pawel Laszczak wrote:
> Patch implements a set of function related to enumeration process.
> Some standard requests are handled on controller driver level and
> other are delegated to gadget core driver.
> All class requests are delegated to gadget core driver.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/ep0.c    | 491 ++++++++++++++++++++++++++++++++++++-
>  drivers/usb/cdns3/gadget.c | 119 +++++++++
>  drivers/usb/cdns3/gadget.h |   4 +
>  3 files changed, 610 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
> index eb92fd234bd7..6f33d98f7684 100644
> --- a/drivers/usb/cdns3/ep0.c
> +++ b/drivers/usb/cdns3/ep0.c
> @@ -10,6 +10,7 @@
>   *	    Peter Chen <peter.chen@nxp.com>
>   */
>  
> +#include <linux/usb/composite.h>
>  #include "gadget.h"
>  
>  static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
> @@ -52,9 +53,31 @@ static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
>  		writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
>  }
>  
> +/**
> + * cdns3_ep0_delegate_req - Returns status of handling setup packet
> + * Setup is handled by gadget driver
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns zero on success or negative value on failure
> + */
> +static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev,
> +				  struct usb_ctrlrequest *ctrl_req)
> +{
> +	int ret;
> +
> +	spin_unlock(&priv_dev->lock);
> +	priv_dev->setup_pending = 1;
> +	ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req);
> +	priv_dev->setup_pending = 0;

Why is setup_pending flag being set and cleared?

> +	spin_lock(&priv_dev->lock);
> +	return ret;
> +}
> +
>  static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>  {
> -	//TODO: Implements this function
> +	priv_dev->ep0_data_dir = 0;
> +	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 8, 0);

why hardcode to 8?
Don't vendor specific requests have different lengths?

>  }
>  
>  static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
> @@ -90,9 +113,431 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>  	}
>  }
>  
> +/**
> + * cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns 0 if success, 0x7FFF on deferred status stage, error code on error

what is this magic number 0x7fff?

> + */
> +static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev,
> +					   struct usb_ctrlrequest *ctrl_req)
> +{
> +	enum usb_device_state device_state = priv_dev->gadget.state;
> +	struct cdns3_endpoint *priv_ep, *temp_ep;
> +	u32 config = le16_to_cpu(ctrl_req->wValue);
> +	int result = 0;
> +
> +	switch (device_state) {
> +	case USB_STATE_ADDRESS:
> +		/* Configure non-control EPs */
> +		list_for_each_entry_safe(priv_ep, temp_ep,
> +					 &priv_dev->ep_match_list,
> +					 ep_match_pending_list)
> +			cdns3_ep_config(priv_ep);

Why configure here? They should be configured at ep_enable. no?
And you don't need to maintain a ep_match/pending_list.

> +
> +		result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
> +
> +		if (result)
> +			return result;
> +
> +		if (config) {

What if result is USB_GADGET_DELAYED_STATUS?

> +			cdns3_set_hw_configuration(priv_dev);

usb_gadget_set_state(USB_STATE_CONFIGURED) ?

> +		} else {
> +			cdns3_gadget_unconfig(priv_dev);
> +			usb_gadget_set_state(&priv_dev->gadget,
> +					     USB_STATE_ADDRESS);
> +		}
> +		break;
> +	case USB_STATE_CONFIGURED:
> +		result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
> +
> +		if (!config && !result) {
> +			cdns3_gadget_unconfig(priv_dev);
> +			usb_gadget_set_state(&priv_dev->gadget,
> +					     USB_STATE_ADDRESS);
> +		}
> +		break;
> +	default:
> +		result = -EINVAL;
> +	}
> +
> +	return result;
> +}
> +
> +/**
> + * cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns 0 if success, error code on error
> + */
> +static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev,
> +				     struct usb_ctrlrequest *ctrl_req)
> +{
> +	enum usb_device_state device_state = priv_dev->gadget.state;
> +	u32 reg;
> +	u32 addr;
> +
> +	addr = le16_to_cpu(ctrl_req->wValue);
> +
> +	if (addr > DEVICE_ADDRESS_MAX) {

If DEVICE_ADDRESS_MAX comes from USB spec it must be in ch9.h.
Maybe add something like

#define	USB_DEVICE_MAX_ADDRESS	127

> +		dev_err(&priv_dev->dev,
> +			"Device address (%d) cannot be greater than %d\n",
> +			addr, DEVICE_ADDRESS_MAX);
> +		return -EINVAL;
> +	}
> +
> +	if (device_state == USB_STATE_CONFIGURED) {
> +		dev_err(&priv_dev->dev, "USB device already configured\n");

Message is misleading. How about "can't set_address from configured state"

> +		return -EINVAL;
> +	}
> +
> +	reg = readl(&priv_dev->regs->usb_cmd);
> +
> +	writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR,
> +	       &priv_dev->regs->usb_cmd);
> +
> +	usb_gadget_set_state(&priv_dev->gadget,
> +			     (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT));
> +
> +	cdns3_prepare_setup_packet(priv_dev);

why call this here? This should be done after the current ep0 request is complete.

> +
> +	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
> +
> +	return 0;
> +}
> +
> +/**
> + * cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns 0 if success, error code on error
> + */
> +static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev,
> +				    struct usb_ctrlrequest *ctrl)
> +{
> +	__le16 *response_pkt;
> +	u16 usb_status = 0;
> +	u32 recip;
> +	u32 reg;
> +
> +	recip = ctrl->bRequestType & USB_RECIP_MASK;
> +
> +	switch (recip) {
> +	case USB_RECIP_DEVICE:
> +		/* self powered */
> +		usb_status |= priv_dev->gadget.is_selfpowered;

if (prv_devgadget.is_selfpowered)
	usb_stats |= BIT(USB_DEVICE_SELF_POWERED);

> +
> +		if (priv_dev->gadget.speed != USB_SPEED_SUPER)

You should check controller speed directly instead.

> +			break;
> +
> +		reg = readl(&priv_dev->regs->usb_sts);
> +
> +		if (priv_dev->u1_allowed)
> +			usb_status |= BIT(USB_DEV_STAT_U1_ENABLED);
> +
> +		if (priv_dev->u2_allowed)
> +			usb_status |= BIT(USB_DEV_STAT_U2_ENABLED);
> +
> +		if (priv_dev->wake_up_flag)
> +			usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP);

Remote wakeup is not SS specific. So needs to go before the SS check.

> +		break;
> +	case USB_RECIP_INTERFACE:
> +		return cdns3_ep0_delegate_req(priv_dev, ctrl);
> +	case USB_RECIP_ENDPOINT:
> +		/* check if endpoint is stalled */
> +		cdns3_select_ep(priv_dev, ctrl->wIndex);
> +		if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)))
> +			usb_status =  BIT(USB_ENDPOINT_HALT);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	response_pkt = (__le16 *)priv_dev->setup;
> +	*response_pkt = cpu_to_le16(usb_status);
> +
> +	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
> +			       sizeof(*response_pkt), 1);
> +	return 0;
> +}
> +
> +static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev,
> +					   struct usb_ctrlrequest *ctrl,
> +					   int set)
> +{
> +	enum usb_device_state state;
> +	enum usb_device_speed speed;
> +	int ret = 0;
> +	u32 wValue;
> +	u32 wIndex;
> +	u16 tmode;
> +
> +	wValue = le16_to_cpu(ctrl->wValue);
> +	wIndex = le16_to_cpu(ctrl->wIndex);
> +	state = priv_dev->gadget.state;
> +	speed = priv_dev->gadget.speed;
> +
> +	switch (ctrl->wValue) {
> +	case USB_DEVICE_REMOTE_WAKEUP:
> +		priv_dev->wake_up_flag = !!set;
> +		break;
> +	case USB_DEVICE_U1_ENABLE:
> +		if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
> +			return -EINVAL;
> +
> +		priv_dev->u1_allowed = !!set;
> +		break;
> +	case USB_DEVICE_U2_ENABLE:
> +		if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
> +			return -EINVAL;
> +
> +		priv_dev->u2_allowed = !!set;
> +		break;
> +	case USB_DEVICE_LTM_ENABLE:
> +		ret = -EINVAL;
> +		break;
> +	case USB_DEVICE_TEST_MODE:
> +		if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH)
> +			return -EINVAL;
> +
> +		tmode = le16_to_cpu(ctrl->wIndex);
> +
> +		if (!set || (tmode & 0xff) != 0)
> +			return -EINVAL;
> +
> +		switch (tmode >> 8) {
> +		case TEST_J:
> +		case TEST_K:
> +		case TEST_SE0_NAK:
> +		case TEST_PACKET:
> +			cdns3_set_register_bit(&priv_dev->regs->usb_cmd,
> +					       USB_CMD_STMODE |
> +					       USB_STS_TMODE_SEL(tmode - 1));
> +			break;
> +		default:
> +			ret = -EINVAL;
> +		}
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev,
> +					 struct usb_ctrlrequest *ctrl,
> +					 int set)
> +{
> +	u32 wValue;
> +	int ret = 0;
> +
> +	wValue = le16_to_cpu(ctrl->wValue);
> +
> +	switch (wValue) {
> +	case USB_INTRF_FUNC_SUSPEND:
> +		break;
> +	default:
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev,
> +					     struct usb_ctrlrequest *ctrl,
> +					     int set)
> +{
> +	struct cdns3_endpoint *priv_ep;
> +	int ret = 0;
> +	u8 index;
> +
> +	if (!(ctrl->wIndex &  ~USB_DIR_IN))
> +		return 0;

Why is this check?

> +
> +	index = cdns3_ep_addr_to_index(ctrl->wIndex);
> +	priv_ep = priv_dev->eps[index];
> +
> +	cdns3_select_ep(priv_dev, ctrl->wIndex);
> +
> +	if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT)
> +		return -EINVAL;

This check should be done first before you try to decode wIndex.

> +
> +	if (set) {
> +		writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
> +		priv_ep->flags |= EP_STALL;
> +	} else {
> +		struct usb_request *request;
> +
> +		if (priv_dev->eps[index]->flags & EP_WEDGE) {
> +			cdns3_select_ep(priv_dev, 0x00);
> +			return 0;
> +		}
> +
> +		writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
> +
> +		/* wait for EPRST cleared */
> +		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
> +				      EP_CMD_EPRST, 0, 100);
> +		if (ret)
> +			return -EINVAL;
> +
> +		priv_ep->flags &= ~EP_STALL;
> +
> +		request = cdns3_next_request(&priv_ep->request_list);
> +		if (request)
> +			cdns3_ep_run_transfer(priv_ep, request);
> +	}
> +	return ret;
> +}
> +
> +/**
> + * cdns3_req_ep0_handle_feature -
> + * Handling of GET/SET_FEATURE standard USB request
> + *
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + * @set: must be set to 1 for SET_FEATURE request
> + *
> + * Returns 0 if success, error code on error
> + */
> +static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev,
> +					struct usb_ctrlrequest *ctrl,
> +					int set)
> +{
> +	int ret = 0;
> +	u32 recip;
> +
> +	recip = ctrl->bRequestType & USB_RECIP_MASK;
> +
> +	switch (recip) {
> +	case USB_RECIP_DEVICE:
> +		ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set);
> +		break;
> +	case USB_RECIP_INTERFACE:
> +		ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set);
> +		break;
> +	case USB_RECIP_ENDPOINT:
> +		ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (!ret)
> +		writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
> +
> +	return ret;
> +}
> +
> +/**
> + * cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns 0 if success, error code on error
> + */
> +static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev,
> +				 struct usb_ctrlrequest *ctrl_req)
> +{
> +	if (priv_dev->gadget.state < USB_STATE_ADDRESS)
> +		return -EINVAL;
> +
> +	if (ctrl_req->wLength != 6) {
> +		dev_err(&priv_dev->dev, "Set SEL should be 6 bytes, got %d\n",
> +			ctrl_req->wLength);
> +		return -EINVAL;
> +	}
> +
> +	priv_dev->ep0_data_dir = 0;
> +	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1);
> +	return 0;
> +}
> +
> +/**
> + * cdns3_req_ep0_set_isoch_delay -
> + * Handling of GET_ISOCH_DELAY standard USB request
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns 0 if success, error code on error
> + */
> +static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev,
> +					 struct usb_ctrlrequest *ctrl_req)
> +{
> +	if (ctrl_req->wIndex || ctrl_req->wLength)
> +		return -EINVAL;
> +
> +	priv_dev->isoch_delay = ctrl_req->wValue;
> +	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
> +	return 0;
> +}
> +
> +/**
> + * cdns3_ep0_standard_request - Handling standard USB requests
> + * @priv_dev: extended gadget object
> + * @ctrl_req: pointer to received setup packet
> + *
> + * Returns 0 if success, error code on error
> + */
> +static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev,
> +				      struct usb_ctrlrequest *ctrl_req)
> +{
> +	int ret;
> +
> +	switch (ctrl_req->bRequest) {
> +	case USB_REQ_SET_ADDRESS:
> +		ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req);
> +		break;
> +	case USB_REQ_SET_CONFIGURATION:
> +		ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req);
> +		break;
> +	case USB_REQ_GET_STATUS:
> +		ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req);
> +		break;
> +	case USB_REQ_CLEAR_FEATURE:
> +		ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0);
> +		break;
> +	case USB_REQ_SET_FEATURE:
> +		ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1);
> +		break;
> +	case USB_REQ_SET_SEL:
> +		ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req);
> +		break;
> +	case USB_REQ_SET_ISOCH_DELAY:
> +		ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req);
> +		break;
> +	default:
> +		ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
>  static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
>  {
> -	//TODO: Implements this function
> +	struct usb_request *request = priv_dev->pending_status_request;
> +
> +	if (priv_dev->status_completion_no_call && request &&
> +	    request->complete) {
> +		request->complete(priv_dev->gadget.ep0, request);
> +		priv_dev->status_completion_no_call = 0;
> +	}
> +}
> +
> +void cdns3_pending_setup_status_handler(struct work_struct *work)
> +{
> +	struct cdns3_device *priv_dev = container_of(work, struct cdns3_device,
> +			pending_status_wq);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&priv_dev->lock, flags);
> +	__pending_setup_status_handler(priv_dev);
> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>  }
>  
>  /**
> @@ -101,12 +546,50 @@ static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
>   */
>  static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
>  {
> -	//TODO: Implements this function.
> +	struct usb_ctrlrequest *ctrl = priv_dev->setup;
> +	int result;
> +
> +	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
> +		result = cdns3_ep0_standard_request(priv_dev, ctrl);
> +	else
> +		result = cdns3_ep0_delegate_req(priv_dev, ctrl);
> +
> +	if (result != 0 && result != USB_GADGET_DELAYED_STATUS) {
> +		dev_dbg(&priv_dev->dev, "STALL for ep0\n");
> +		/* set_stall on ep0 */
> +		cdns3_select_ep(priv_dev, 0x00);
> +		writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
> +		writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
> +	}
>  }
>  
>  static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
>  {
> -	//TODO: Implements this function
> +	if (priv_dev->ep0_request) {
> +		usb_gadget_unmap_request_by_dev(priv_dev->sysdev,
> +						priv_dev->ep0_request,
> +						priv_dev->ep0_data_dir);
> +
> +		priv_dev->ep0_request->actual =
> +			TRB_LEN(le32_to_cpu(priv_dev->trb_ep0->length));
> +
> +		dev_dbg(&priv_dev->dev, "Ep0 completion length %d\n",
> +			priv_dev->ep0_request->actual);
> +		list_del_init(&priv_dev->ep0_request->list);
> +	}
> +
> +	if (priv_dev->ep0_request &&
> +	    priv_dev->ep0_request->complete) {
> +		spin_unlock(&priv_dev->lock);
> +		priv_dev->ep0_request->complete(priv_dev->gadget.ep0,
> +						priv_dev->ep0_request);
> +
> +		priv_dev->ep0_request = NULL;
> +		spin_lock(&priv_dev->lock);
> +	}
> +
> +	cdns3_prepare_setup_packet(priv_dev);
> +	writel(EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>  }
>  
>  /**
> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
> index 309202474e57..0202ff5f6c90 100644
> --- a/drivers/usb/cdns3/gadget.c
> +++ b/drivers/usb/cdns3/gadget.c
> @@ -70,6 +70,30 @@ static u8 cdns3_ep_reg_pos_to_index(int i)
>  	return ((i / 16) + (((i % 16) - 2) * 2));
>  }
>  
> +/**
> + * cdns3_ep_addr_to_index - Macro converts endpoint address to
> + * index of endpoint object in cdns3_device.eps[] container
> + * @ep_addr: endpoint address for which endpoint object is required
> + *
> + * Remember that endpoint container doesn't contain default endpoint
> + */
> +u8 cdns3_ep_addr_to_index(u8 ep_addr)
> +{
> +	return (((ep_addr & 0x7F) - 1) + ((ep_addr & USB_DIR_IN) ? 1 : 0));
> +}
> +
> +/**
> + * cdns3_ep_addr_to_bit_pos - Macro converts endpoint address to
> + * bit position in ep_ists register
> + * @ep_addr: endpoint address for which bit position is required
> + *
> + * Remember that endpoint container doesn't contain default endpoint
> + */
> +static u32 cdns3_ep_addr_to_bit_pos(u8 ep_addr)
> +{
> +	return (1 << (ep_addr & 0x7F)) << ((ep_addr & 0x80) ? 16 : 0);
> +}
> +
>  /**
>   * cdns3_next_request - returns next request from list
>   * @list: list containing requests
> @@ -464,6 +488,99 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>  	return ret;
>  }
>  
> +/**
> + * cdns3_ep_onchip_buffer_alloc - Try to allocate onchip buf for EP
> + *
> + * The real allocation will occur during write to EP_CFG register,
> + * this function is used to check if the 'size' allocation is allowed.
> + *
> + * @priv_dev: extended gadget object
> + * @size: the size (KB) for EP would like to allocate
> + * @is_in: the direction for EP
> + *
> + * Return 0 if the later allocation is allowed or negative value on failure
> + */
> +static int cdns3_ep_onchip_buffer_alloc(struct cdns3_device *priv_dev,
> +					int size, int is_in)
> +{
> +	if (is_in) {
> +		priv_dev->onchip_mem_allocated_size += size;
> +	} else if (!priv_dev->out_mem_is_allocated) {
> +		 /* ALL OUT EPs are shared the same chunk onchip memory */
> +		priv_dev->onchip_mem_allocated_size += size;
> +		priv_dev->out_mem_is_allocated = 1;
> +	}
> +
> +	if (priv_dev->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) {
> +		priv_dev->onchip_mem_allocated_size -= size;
> +		return -EPERM;
> +	} else {
> +		return 0;
> +	}
> +}
> +
> +/**
> + * cdns3_ep_config Configure hardware endpoint
> + * @priv_ep: extended endpoint object
> + */
> +void cdns3_ep_config(struct cdns3_endpoint *priv_ep)
> +{
> +	bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC);
> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
> +	u32 bEndpointAddress = priv_ep->num | priv_ep->dir;
> +	u32 interrupt_mask = EP_STS_EN_TRBERREN;
> +	u32 max_packet_size = 0;
> +	u32 ep_cfg = 0;
> +	int ret;
> +
> +	if (priv_ep->type == USB_ENDPOINT_XFER_INT) {
> +		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT);
> +	} else if (priv_ep->type == USB_ENDPOINT_XFER_BULK) {
> +		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK);
> +	} else {
> +		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC);
> +		interrupt_mask = 0xFFFFFFFF;
> +	}
> +
> +	switch (priv_dev->gadget.speed) {
> +	case USB_SPEED_FULL:
> +		max_packet_size = is_iso_ep ? 1023 : 64;
> +		break;
> +	case USB_SPEED_HIGH:
> +		max_packet_size = is_iso_ep ? 1024 : 512;
> +		break;
> +	case USB_SPEED_SUPER:
> +		max_packet_size = 1024;
> +		break;
> +	default:
> +		//all other speed are not supported
> +		return;
> +	}
> +
> +	ret = cdns3_ep_onchip_buffer_alloc(priv_dev, CDNS3_EP_BUF_SIZE,
> +					   priv_ep->dir);

where do you free the buffer_alloc?

> +	if (ret) {
> +		dev_err(&priv_dev->dev, "onchip mem is full, ep is invalid\n");
> +		return;
> +	}
> +
> +	ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) |
> +		  EP_CFG_BUFFERING(CDNS3_EP_BUF_SIZE - 1) |
> +		  EP_CFG_MAXBURST(priv_ep->endpoint.maxburst);
> +
> +	cdns3_select_ep(priv_dev, bEndpointAddress);
> +
> +	writel(ep_cfg, &priv_dev->regs->ep_cfg);
> +	writel(interrupt_mask, &priv_dev->regs->ep_sts_en);
> +
> +	dev_dbg(&priv_dev->dev, "Configure %s: with val %08x\n",
> +		priv_ep->name, ep_cfg);
> +
> +	/* enable interrupt for selected endpoint */
> +	cdns3_set_register_bit(&priv_dev->regs->ep_ien,
> +			       cdns3_ep_addr_to_bit_pos(bEndpointAddress));
> +}
> +
>  /* Find correct direction for HW endpoint according to description */
>  static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
>  				   struct cdns3_endpoint *priv_ep)
> @@ -1104,6 +1221,8 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
>  	priv_dev->is_connected = 0;
>  
>  	spin_lock_init(&priv_dev->lock);
> +	INIT_WORK(&priv_dev->pending_status_wq,
> +		  cdns3_pending_setup_status_handler);
>  
>  	priv_dev->in_standby_mode = 1;
>  
> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
> index 8c2f363f9340..db8c6cb9f2a5 100644
> --- a/drivers/usb/cdns3/gadget.h
> +++ b/drivers/usb/cdns3/gadget.h
> @@ -1070,14 +1070,18 @@ struct cdns3_device {
>  
>  int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
> +void cdns3_pending_setup_status_handler(struct work_struct *work);
>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
> +void cdns3_ep_config(struct cdns3_endpoint *priv_ep);
>  void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
>  struct usb_request *cdns3_next_request(struct list_head *list);
> +void cdns3_gadget_unconfig(struct cdns3_device *priv_dev);
>  int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>  			  struct usb_request *request);
> +u8 cdns3_ep_addr_to_index(u8 ep_addr);
>  int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
>  int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
>  struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
> 

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization.
  2018-11-28 11:40     ` Felipe Balbi
@ 2018-11-30  4:20       ` PETER CHEN
  2018-11-30  6:29         ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: PETER CHEN @ 2018-11-30  4:20 UTC (permalink / raw)
  To: Felipe Balbi, Roger Quadros, Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, linux-kernel, adouglas, jbergsagel, nsekhar,
	nm, sureshp, pjez, kurahul

 
> Roger Quadros <rogerq@ti.com> writes:
> >> +static void cdns3_gadget_config(struct cdns3_device *priv_dev) {
> >> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
> >> +
> >> +	cdns3_ep0_config(priv_dev);
> >> +
> >> +	/* enable interrupts for endpoint 0 (in and out) */
> >> +	writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, &regs->ep_ien);
> >> +
> >> +	/* enable generic interrupt*/
> >> +	writel(USB_IEN_INIT, &regs->usb_ien);
> >> +	writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, &regs->usb_conf);
> >> +	writel(USB_CONF_DMULT, &regs->usb_conf);
> >> +	writel(USB_CONF_DEVEN, &regs->usb_conf);
> >
> > If you are enabling interrupts in this patch you should handle them in the ISR.
> 
> Frankly, I don't understand why this is a series. It's a single driver and splitting it into
> a series just makes it more difficult to review, actually.
> 
> Sure, a single patch will be large, but there's no way to have a functional driver until
> all patches are applied, anyway.
> 

Yes, I agree with Felipe. Pawel, you could remove the "RFC" prefix, and send the whole
one as one patch. I will test it at my hardware.

Peter

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

* RE: [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization.
  2018-11-30  4:20       ` PETER CHEN
@ 2018-11-30  6:29         ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-30  6:29 UTC (permalink / raw)
  To: PETER CHEN, Felipe Balbi, Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, Pawel Jez, Rahul Kumar

Hi,

>> Roger Quadros <rogerq@ti.com> writes:
>> >> +static void cdns3_gadget_config(struct cdns3_device *priv_dev) {
>> >> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>> >> +
>> >> +	cdns3_ep0_config(priv_dev);
>> >> +
>> >> +	/* enable interrupts for endpoint 0 (in and out) */
>> >> +	writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, &regs->ep_ien);
>> >> +
>> >> +	/* enable generic interrupt*/
>> >> +	writel(USB_IEN_INIT, &regs->usb_ien);
>> >> +	writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, &regs->usb_conf);
>> >> +	writel(USB_CONF_DMULT, &regs->usb_conf);
>> >> +	writel(USB_CONF_DEVEN, &regs->usb_conf);
>> >
>> > If you are enabling interrupts in this patch you should handle them in the ISR.
>>
>> Frankly, I don't understand why this is a series. It's a single driver and splitting it into
>> a series just makes it more difficult to review, actually.
>>
>> Sure, a single patch will be large, but there's no way to have a functional driver until
>> all patches are applied, anyway.
>>
>
>Yes, I agree with Felipe. Pawel, you could remove the "RFC" prefix, and send the whole
>one as one patch. I will test it at my hardware.

Ok, I will prepare such single patch.  
Peter I know that you have little different platform. 
Probably you should made some additional changes for your platform. 

I assume that core.c should be common file. 
Probably we should add also some platform specific file. 

Thanks.  

Pawel.

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

* RE: [RFC PATCH v2 02/15] usb:cdns3: Device side header file.
  2018-11-18 10:08 ` [RFC PATCH v2 02/15] usb:cdns3: Device side header file Pawel Laszczak
@ 2018-11-30  6:48   ` PETER CHEN
  2018-12-02 19:27     ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: PETER CHEN @ 2018-11-30  6:48 UTC (permalink / raw)
  To: Pawel Laszczak, devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, adouglas, jbergsagel,
	nsekhar, nm, sureshp, pjez, kurahul

 
 
> +
> +/*
> + * USBSS-DEV register interface.
> + * This corresponds to the USBSS Device Controller Interface  */
> +/**
> + * struct xhci_cap_regs - xHCI Host Controller Capability Registers.

struct cdns3_usb_regs - device controller registers

> +struct cdns3_device;
> +
> +struct cdns3_endpoint {
> +	struct usb_ep		endpoint;
> +	struct list_head	request_list;
> +	struct list_head	ep_match_pending_list;
> +
> +	struct cdns3_trb	*trb_pool;
> +	dma_addr_t		trb_pool_dma;
> +
> +	struct cdns3_device	*cdns3_dev;
> +	char			name[20];
> +
> +#define EP_ENABLED		BIT(0)
> +#define EP_STALL		BIT(1)
> +#define EP_WEDGE		BIT(2)
> +#define EP_TRANSFER_STARTED	BIT(3)
> +#define EP_UPDATE_EP_TRBADDR	BIT(4)
> +#define EP_PENDING_REQUEST	BIT(5)
> +#define EP_USED			BIT(5)
> +	u32			flags;
> +
> +	void			*aligned_buff;
> +	dma_addr_t		aligned_dma_addr;
> +	u8			dir;
> +	u8			num;
> +	u8			type;
> +
> +	int			free_trbs;
> +	u8			pcs;
> +	u8			ccs;
> +	int			enqueue;
> +	int			dequeue;
> +};
> +

Would you please add kernel doc for above structure?

> +struct cdns3_request {
> +	struct usb_request request;
> +	struct cdns3_endpoint *priv_ep;
> +	struct cdns3_trb *trb;
> +	int start_trb;
> +	int end_trb;
> +	int on_ring:1;
> +};
> +
> +#define to_cdns3_request(r) (container_of(r, struct cdns3_request,
> +request))
> +
> +struct cdns3_device {
> +	struct device			dev;
> +	struct cdns3_usb_regs		__iomem *regs;
> +
> +	struct usb_gadget		gadget;
> +	struct usb_gadget_driver	*gadget_driver;
> +
> +	struct usb_ctrlrequest		*setup;
> +	dma_addr_t			setup_dma;
> +	dma_addr_t			trb_ep0_dma;
> +	struct cdns3_trb		*trb_ep0;
> +	void				*zlp_buf;
> +
> +	struct cdns3_endpoint		*eps[USB_SS_ENDPOINTS_MAX_COUNT];
> +	int				ep_nums;
> +	/*
> +	 * field used for improving performance. It holds the last
> +	 * selected endpoint number
> +	 */
> +	u32				selected_ep;
> +	struct usb_request		*ep0_request;
> +	int				ep0_data_dir;
> +	int				hw_configured_flag;
> +	int				wake_up_flag;
> +	u16				isoch_delay;
> +	/* generic spin-lock for drivers */
> +	spinlock_t			lock;
> +
> +	unsigned			is_connected:1;
> +	unsigned			in_standby_mode:1;
> +	unsigned			status_completion_no_call:1;
> +	unsigned			u1_allowed:1;
> +	unsigned			u2_allowed:1;
> +
> +	u32				usb_ien;
> +	u32				ep_ien;
> +	int				setup_pending;
> +	struct device			*sysdev;
> +	/* The device mode is enabled */
> +	int				start_gadget;
> +	struct list_head		ep_match_list;
> +	/* KB */
> +	int				onchip_mem_allocated_size;
> +	/* Memory is allocated for OUT */
> +	int				out_mem_is_allocated:1;
> +	struct work_struct		pending_status_wq;
> +	struct usb_request		*pending_status_request;
> +};
> +

kernel-doc please

Peter

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-18 10:09 ` [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code Pawel Laszczak
  2018-11-23 11:35   ` Roger Quadros
@ 2018-11-30  7:32   ` Peter Chen
  2018-12-02 20:34     ` Pawel Laszczak
  1 sibling, 1 reply; 85+ messages in thread
From: Peter Chen @ 2018-11-30  7:32 UTC (permalink / raw)
  To: pawell
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

On Sun, Nov 18, 2018 at 6:16 PM Pawel Laszczak <pawell@cadence.com> wrote:
>
> Patch adds core.c and core.h file that implements initialization
> of platform driver and adds function responsible for selecting,
> switching and running appropriate Device/Host mode.
>
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/Makefile |   2 +
>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
>  drivers/usb/cdns3/core.h   | 100 +++++++++
>  3 files changed, 515 insertions(+)
>  create mode 100644 drivers/usb/cdns3/core.c
>  create mode 100644 drivers/usb/cdns3/core.h
>
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> index dcdd62003c6a..02d25b23c5d3 100644
> --- a/drivers/usb/cdns3/Makefile
> +++ b/drivers/usb/cdns3/Makefile
> @@ -1,3 +1,5 @@
> +obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
>
> +cdns3-y                                        := core.o
>  cdns3-pci-y                            := cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> new file mode 100644
> index 000000000000..f9055d4da67f
> --- /dev/null
> +++ b/drivers/usb/cdns3/core.c
> @@ -0,0 +1,413 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver.
> + *
> + * Copyright (C) 2018 Cadence.
> + *

Please add NXP copyright too.

> + * Author: Peter Chen <peter.chen@nxp.com>
> + *         Pawel Laszczak <pawell@cadence.com>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "gadget.h"
> +#include "core.h"
> +
> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> +{
> +       WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
> +       return cdns->roles[cdns->role];
> +}
> +

Can we delete "current", and use cdns3_get_role_driver directly?

> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
> +{
> +       int ret;
> +
> +       if (role >= CDNS3_ROLE_END)
> +               return 0;
> +
> +       if (!cdns->roles[role])
> +               return -ENXIO;
> +
> +       mutex_lock(&cdns->mutex);
> +       cdns->role = role;
> +       ret = cdns->roles[role]->start(cdns);
> +       mutex_unlock(&cdns->mutex);
> +       return ret;
> +}
> +
> +static inline void cdns3_role_stop(struct cdns3 *cdns)
> +{
> +       enum cdns3_roles role = cdns->role;
> +
> +       if (role == CDNS3_ROLE_END)
> +               return;
> +
> +       mutex_lock(&cdns->mutex);
> +       cdns->roles[role]->stop(cdns);
> +       cdns->role = CDNS3_ROLE_END;
> +       mutex_unlock(&cdns->mutex);
> +}
> +
> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
> +{
> +       if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> +               //TODO: implements selecting device/host mode
> +               return CDNS3_ROLE_HOST;
> +       }
> +       return cdns->roles[CDNS3_ROLE_HOST]
> +               ? CDNS3_ROLE_HOST
> +               : CDNS3_ROLE_GADGET;
> +}
> +
> +/**
> + * cdns3_core_init_role - initialize role of operation
> + * @cdns: Pointer to cdns3 structure
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_core_init_role(struct cdns3 *cdns)
> +{
> +       struct device *dev = cdns->dev;
> +       enum usb_dr_mode dr_mode;
> +
> +       dr_mode = usb_get_dr_mode(dev);
> +       cdns->role = CDNS3_ROLE_END;
> +
> +       /*
> +        * If driver can't read mode by means of usb_get_dr_mdoe function then
> +        * chooses mode according with Kernel configuration. This setting
> +        * can be restricted later depending on strap pin configuration.
> +        */
> +       if (dr_mode == USB_DR_MODE_UNKNOWN) {
> +               if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
> +                   IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> +                       dr_mode = USB_DR_MODE_OTG;
> +               else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
> +                       dr_mode = USB_DR_MODE_HOST;
> +               else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> +                       dr_mode = USB_DR_MODE_PERIPHERAL;
> +       }
> +
> +       if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
> +               //TODO: implements host initialization
> +       }
> +
> +       if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
> +               //TODO: implements device initialization
> +       }
> +
> +       if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
> +               dev_err(dev, "no supported roles\n");
> +               return -ENODEV;
> +       }
> +
> +       cdns->dr_mode = dr_mode;
> +       return 0;
> +}
> +
> +/**
> + * cdns3_irq - interrupt handler for cdns3 core device
> + *
> + * @irq: irq number for cdns3 core device
> + * @data: structure of cdns3
> + *
> + * Returns IRQ_HANDLED or IRQ_NONE
> + */
> +static irqreturn_t cdns3_irq(int irq, void *data)
> +{
> +       struct cdns3 *cdns = data;
> +       irqreturn_t ret = IRQ_NONE;
> +
> +       /* Handle device/host interrupt */
> +       if (cdns->role != CDNS3_ROLE_END)
> +               ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
> +
> +       return ret;
> +}
> +
> +static void cdns3_remove_roles(struct cdns3 *cdns)
> +{
> +       //TODO: implements this function
> +}
> +
> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
> +{
> +       enum cdns3_roles current_role;
> +       int ret = 0;
> +
> +       current_role = cdns->role;
> +
> +       if (role == CDNS3_ROLE_END)
> +               return 0;
> +
> +       dev_dbg(cdns->dev, "Switching role");
> +
> +       ret = cdns3_role_start(cdns, role);
> +       if (ret) {
> +               /* Back to current role */
> +               dev_err(cdns->dev, "set %d has failed, back to %d\n",
> +                       role, current_role);
> +               ret = cdns3_role_start(cdns, current_role);
> +       }
> +
> +       return ret;
> +}
> +
> +/**
> + * cdns3_role_switch - work queue handler for role switch
> + *
> + * @work: work queue item structure
> + *
> + * Handles below events:
> + * - Role switch for dual-role devices
> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
> + */
> +static void cdns3_role_switch(struct work_struct *work)
> +{
> +       enum cdns3_roles role = CDNS3_ROLE_END;
> +       struct cdns3 *cdns;
> +       bool device, host;
> +
> +       cdns = container_of(work, struct cdns3, role_switch_wq);
> +
> +       //TODO: implements this functions.
> +       //host = cdns3_is_host(cdns);
> +       //device = cdns3_is_device(cdns);

You may improve use C comment.

> +       host = 1;
> +       device = 0;
> +
> +       if (host)
> +               role = CDNS3_ROLE_HOST;
> +       else if (device)
> +               role = CDNS3_ROLE_GADGET;
> +
> +       if (cdns->desired_dr_mode == cdns->current_dr_mode &&
> +           cdns->role == role)
> +               return;
> +
> +       pm_runtime_get_sync(cdns->dev);
> +       cdns3_role_stop(cdns);
> +
> +       if (host) {
> +               if (cdns->roles[CDNS3_ROLE_HOST])
> +                       cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
> +               pm_runtime_put_sync(cdns->dev);
> +               return;
> +       }
> +
> +       if (device)
> +               cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
> +       else
> +               cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
> +
> +       pm_runtime_put_sync(cdns->dev);
> +}
> +
> +/**
> + * cdns3_probe - probe for cdns3 core device
> + * @pdev: Pointer to cdns3 core platform device
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct resource *res;
> +       struct cdns3 *cdns;
> +       void __iomem *regs;
> +       int ret;
> +
> +       cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
> +       if (!cdns)
> +               return -ENOMEM;
> +
> +       cdns->dev = dev;
> +
> +       platform_set_drvdata(pdev, cdns);
> +
> +       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> +       if (!res) {
> +               dev_err(dev, "missing IRQ\n");
> +               return -ENODEV;
> +       }
> +       cdns->irq = res->start;
> +
> +       /*
> +        * Request memory region
> +        * region-0: xHCI
> +        * region-1: Peripheral
> +        * region-2: OTG registers
> +        */
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       regs = devm_ioremap_resource(dev, res);
> +
> +       if (IS_ERR(regs))
> +               return PTR_ERR(regs);
> +       cdns->xhci_regs = regs;
> +       cdns->xhci_res = res;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +       regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(regs))
> +               return PTR_ERR(regs);
> +       cdns->dev_regs  = regs;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> +       regs = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(regs))
> +               return PTR_ERR(regs);
> +       cdns->otg_regs = regs;
> +
> +       mutex_init(&cdns->mutex);
> +
> +       cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> +       if (IS_ERR(cdns->phy)) {
> +               dev_info(dev, "no generic phy found\n");
> +               cdns->phy = NULL;
> +               /*
> +                * fall through here!
> +                * if no generic phy found, phy init
> +                * should be done under boot!
> +                */

If the phy driver is defer-probed, it will be here, it is not an error.
I think you could have a generic phy driver or usb generic phy driver
(drivers/usb/phy/phy-generic.c) even you don't need any operations for
PHY. It will be easy for other platforms.

> +       } else {
> +               phy_init(cdns->phy);
> +       }
> +
> +       ret = cdns3_core_init_role(cdns);
> +       if (ret)
> +               goto err1;
> +
> +       INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
> +       if (ret)
> +               goto err2;
> +
> +       if (ret)
> +               goto err2;
> +
> +       cdns->role = cdns3_get_role(cdns);
> +
> +       ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
> +                              dev_name(dev), cdns);
> +
> +       if (ret)
> +               goto err2;
> +
> +       ret = cdns3_role_start(cdns, cdns->role);
> +       if (ret) {
> +               dev_err(dev, "can't start %s role\n",
> +                       cdns3_get_current_role_driver(cdns)->name);
> +               goto err2;
> +       }
> +
> +       device_set_wakeup_capable(dev, true);
> +       pm_runtime_set_active(dev);
> +       pm_runtime_enable(dev);
> +
> +       /*
> +        * The controller needs less time between bus and controller suspend,
> +        * and we also needs a small delay to avoid frequently entering low
> +        * power mode.
> +        */
> +       pm_runtime_set_autosuspend_delay(dev, 20);
> +       pm_runtime_mark_last_busy(dev);
> +       pm_runtime_use_autosuspend(dev);
> +       dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
> +
> +       return 0;
> +
> +err2:
> +       cdns3_remove_roles(cdns);
> +err1:
> +       return ret;
> +}
> +
> +/**
> + * cdns3_remove - unbind drd driver and clean up
> + * @pdev: Pointer to Linux platform device
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static int cdns3_remove(struct platform_device *pdev)
> +{
> +       struct cdns3 *cdns = platform_get_drvdata(pdev);
> +
> +       pm_runtime_get_sync(&pdev->dev);
> +       pm_runtime_disable(&pdev->dev);
> +       pm_runtime_put_noidle(&pdev->dev);
> +       cdns3_remove_roles(cdns);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id of_cdns3_match[] = {
> +       { .compatible = "cdns,usb3" },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, of_cdns3_match);
> +#endif
> +
> +#ifdef CONFIG_PM
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int cdns3_suspend(struct device *dev)
> +{
> +       //TODO: implements this function
> +       return 0;
> +}
> +
> +static int cdns3_resume(struct device *dev)
> +{
> +       //TODO: implements this function
> +       return 0;
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +static int cdns3_runtime_suspend(struct device *dev)
> +{      //TODO: implements this function
> +       return 0;
> +}
> +
> +static int cdns3_runtime_resume(struct device *dev)
> +{
> +       //TODO: implements this function
> +       return 0;
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops cdns3_pm_ops = {
> +       SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
> +       SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
> +};
> +
> +static struct platform_driver cdns3_driver = {
> +       .probe          = cdns3_probe,
> +       .remove         = cdns3_remove,
> +       .driver         = {
> +               .name   = "cdns-usb3",
> +               .of_match_table = of_match_ptr(of_cdns3_match),
> +               .pm     = &cdns3_pm_ops,
> +       },
> +};
> +
> +static int __init cdns3_driver_platform_register(void)
> +{
> +       return platform_driver_register(&cdns3_driver);
> +}
> +module_init(cdns3_driver_platform_register);
> +
> +static void __exit cdns3_driver_platform_unregister(void)
> +{
> +       platform_driver_unregister(&cdns3_driver);
> +}
> +module_exit(cdns3_driver_platform_unregister);
> +
> +MODULE_ALIAS("platform:cdns3");
> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
> new file mode 100644
> index 000000000000..7c8204fe4d3d
> --- /dev/null
> +++ b/drivers/usb/cdns3/core.h
> @@ -0,0 +1,100 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence USBSS DRD Driver.
> + *

Header file

> + * Copyright (C) 2017 NXP
> + * Copyright (C) 2018 Cadence.
> + *
> + * Authors: Peter Chen <peter.chen@nxp.com>
> + *          Pawel Laszczak <pawell@cadence.com>
> + */
> +#include <linux/usb/otg.h>
> +
> +#ifndef __LINUX_CDNS3_CORE_H
> +#define __LINUX_CDNS3_CORE_H
> +
> +struct cdns3;
> +enum cdns3_roles {
> +       CDNS3_ROLE_HOST = 0,
> +       CDNS3_ROLE_GADGET,
> +       CDNS3_ROLE_END,
> +};
> +
> +/**
> + * struct cdns3_role_driver - host/gadget role driver
> + * @start: start this role
> + * @stop: stop this role
> + * @suspend: suspend callback for this role
> + * @resume: resume callback for this role
> + * @irq: irq handler for this role
> + * @name: role name string (host/gadget)
> + */
> +struct cdns3_role_driver {
> +       int (*start)(struct cdns3 *cdns);
> +       void (*stop)(struct cdns3 *cdns);
> +       int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
> +       int (*resume)(struct cdns3 *cdns, bool hibernated);
> +       irqreturn_t (*irq)(struct cdns3 *cdns);
> +       const char *name;
> +};
> +
> +#define CDNS3_NUM_OF_CLKS      5
> +/**
> + * struct cdns3 - Representation of Cadence USB3 DRD controller.
> + * @dev: pointer to Cadence device struct
> + * @xhci_regs: pointer to base of xhci registers
> + * @xhci_res: the resource for xhci
> + * @dev_regs: pointer to base of dev registers
> + * @otg_regs: pointer to base of otg registers
> + * @irq: irq number for controller
> + * @roles: array of supported roles for this controller
> + * @role: current role
> + * @host_dev: the child host device pointer for cdns3 core
> + * @gadget_dev: the child gadget device pointer for cdns3 core
> + * @usb: phy for this controller
> + * @role_switch_wq: work queue item for role switch
> + * @in_lpm: the controller in low power mode
> + * @wakeup_int: the wakeup interrupt
> + * @mutex: the mutex for concurrent code at driver
> + * @dr_mode: supported mode of operation it can be only Host, only Device
> + *           or OTG mode that allow to switch between Device and Host mode.
> + *           This field based on hardware configuration and cant't be changed.

Based on firmware setting, kernel configuration and hardware configuration.

> + * @current_dr_role: current mode of operation when in dual-role mode
> + * @desired_dr_role: desired mode of operation when in dual-role mode.
> + *           This value can be changed during runtime.
> + *           Available options depends on  dr_mode:
> + *           dr_mode                 |  desired_dr_role and current_dr_role
> + *           ----------------------------------------------------------------
> + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
> + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
> + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
> + *
> + *           Desired_dr_role can be changed by means of debugfs.
> + * @root: debugfs root folder pointer
> + */
> +struct cdns3 {
> +       struct device                   *dev;
> +       void __iomem                    *xhci_regs;
> +       struct resource                 *xhci_res;
> +       struct cdns3_usb_regs __iomem   *dev_regs;
> +       struct cdns3_otg_regs           *otg_regs;
> +       int irq;
> +       struct cdns3_role_driver        *roles[CDNS3_ROLE_END];
> +       enum cdns3_roles                role;
> +       struct device                   *host_dev;
> +       struct device                   *gadget_dev;
> +       struct phy                      *phy;
> +       struct work_struct              role_switch_wq;
> +       int                             in_lpm:1;
> +       int                             wakeup_int:1;
> +       /* mutext used in workqueue*/
> +       struct mutex                    mutex;
> +       enum usb_dr_mode                dr_mode;
> +       enum usb_dr_mode                current_dr_mode;
> +       enum usb_dr_mode                desired_dr_mode;
> +       struct dentry                   *root;
> +};
> +
> +#endif /* __LINUX_CDNS3_CORE_H */
> --
> 2.17.1
>

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

* RE: [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization.
  2018-11-28 11:34   ` Roger Quadros
  2018-11-28 11:40     ` Felipe Balbi
@ 2018-11-30 14:36     ` Pawel Laszczak
  1 sibling, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-11-30 14:36 UTC (permalink / raw)
  To: Roger Quadros, devicetree, Felipe Balbi
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi, 
>
>+Felipe.
>
>Pawel,
>
>Please copy Felipe Balbi as he maintains the USB gadget stack.
>
>On 18/11/18 12:09, Pawel Laszczak wrote:
>> Patch implements a set of functions responsible for initialization,
>> configuration, starting and stopping device mode.
>> This patch also adds new ep0.c that holds all functions related
>> to endpoint 0.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/Kconfig         |  10 +
>>  drivers/usb/cdns3/Makefile        |   1 +
>>  drivers/usb/cdns3/core.c          |   5 +-
>>  drivers/usb/cdns3/ep0.c           | 105 ++++++++
>>  drivers/usb/cdns3/gadget-export.h |  27 +++
>>  drivers/usb/cdns3/gadget.c        | 390 ++++++++++++++++++++++++++++++
>>  drivers/usb/cdns3/gadget.h        |   4 +
>>  7 files changed, 541 insertions(+), 1 deletion(-)
>>  create mode 100644 drivers/usb/cdns3/ep0.c
>>  create mode 100644 drivers/usb/cdns3/gadget-export.h
>>  create mode 100644 drivers/usb/cdns3/gadget.c
>>
>> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig
>> index d92bc3d68eb0..b7d71b5c4f60 100644
>> --- a/drivers/usb/cdns3/Kconfig
>> +++ b/drivers/usb/cdns3/Kconfig
>> @@ -10,6 +10,16 @@ config USB_CDNS3
>>
>>  if USB_CDNS3
>>
>> +config USB_CDNS3_GADGET
>> +        bool "Cadence USB3 device controller"
>> +        depends on USB_GADGET
>> +        help
>> +          Say Y here to enable device controller functionality of the
>> +          cadence USBSS-DEV driver.
>> +
>> +          This controller support FF, HS and SS mode. It doeasn't support
>
>s/support/supports
>s/doeasn't/doesn't
>
>> +          LS and SSP mode
>> +
>>  config USB_CDNS3_HOST
>>          bool "Cadence USB3 host controller"
>>          depends on USB_XHCI_HCD
>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> index 976117ba67ff..bea6173bf37f 100644
>> --- a/drivers/usb/cdns3/Makefile
>> +++ b/drivers/usb/cdns3/Makefile
>> @@ -2,5 +2,6 @@ obj-$(CONFIG_USB_CDNS3)			+= cdns3.o
>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)	+= cdns3-pci.o
>>
>>  cdns3-y					:= core.o drd.o
>> +cdns3-$(CONFIG_USB_CDNS3_GADGET)	+= gadget.o ep0.o
>>  cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
>>  cdns3-pci-y		 		:= cdns3-pci-wrap.o
>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> index 4cb820be9ff3..1fa233415901 100644
>> --- a/drivers/usb/cdns3/core.c
>> +++ b/drivers/usb/cdns3/core.c
>> @@ -18,6 +18,7 @@
>>  #include "gadget.h"
>>  #include "core.h"
>>  #include "host-export.h"
>> +#include "gadget-export.h"
>>  #include "drd.h"
>>
>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>> @@ -104,7 +105,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
>>  	}
>>
>>  	if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>> -		//TODO: implements device initialization
>> +		if (cdns3_gadget_init(cdns))
>> +			dev_info(dev, "doesn't support gadget\n");
>
>dev_err() and we should should error out with error code returned by cdns3_gadget_init().
Ok, 
>
>>  	}
>>
>>  	if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>> @@ -144,6 +146,7 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>
>>  static void cdns3_remove_roles(struct cdns3 *cdns)
>>  {
>
>if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL)

I had to  little change this  fragment. In the latest version only single driver (role) can be 
initialized and started so this code has changed to :

static void cdns3_exit_roles(struct cdns3 *cdns)
{
	cdns3_role_stop(cdns);
	cdns3_drd_exit(cdns);
}

Functions  cdns3_gadget_remove and cdns3_host_remove are no longer needed.

>> +	cdns3_gadget_remove(cdns);
>>  	cdns3_host_remove(cdns);
>>  }
>>
>> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
>> new file mode 100644
>> index 000000000000..c08d02665f9d
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/ep0.c
>> @@ -0,0 +1,105 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver - gadget side.
>> + *
>> + * Copyright (C) 2018 Cadence Design Systems.
>> + * Copyright (C) 2017 NXP
>> + *
>> + * Authors: Pawel Jez <pjez@cadence.com>,
>> + *          Pawel Laszczak <pawell@cadence.com>
>> + *	    Peter Chen <peter.chen@nxp.com>
>> + */
>> +
>> +#include "gadget.h"
>> +
>> +static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
>> +	.bLength = USB_DT_ENDPOINT_SIZE,
>> +	.bDescriptorType = USB_DT_ENDPOINT,
>> +	.bmAttributes =	USB_ENDPOINT_XFER_CONTROL,
>> +};
>> +
>> +static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>> +{
>> +	//TODO: Implements this function
>> +}
>> +
>> +/**
>> + * cdns3_ep0_config - Configures default endpoint
>> + * @priv_dev: extended gadget object
>> + *
>> + * Functions sets parameters: maximal packet size and enables interrupts
>> + */
>> +void cdns3_ep0_config(struct cdns3_device *priv_dev)
>> +{
>> +	struct cdns3_usb_regs __iomem *regs;
>> +	u32 max_packet_size = 64;
>> +
>> +	regs = priv_dev->regs;
>> +
>> +	if (priv_dev->gadget.speed == USB_SPEED_SUPER)
>> +		max_packet_size = 512;
>
>is gadget.speed known at this point? I think it will only be known after a connection
>is made.

This function is called on connection, on reset event and during initialization gadget. 
During initialization the speed will be set to 64 and then will be updated to correct value. 

>> +
>> +	if (priv_dev->ep0_request) {
>> +		list_del_init(&priv_dev->ep0_request->list);
>> +		priv_dev->ep0_request = NULL;
>> +	}
>> +
>> +	priv_dev->gadget.ep0->maxpacket = max_packet_size;
>> +	cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
>> +
>> +	/* init ep out */
>> +	cdns3_select_ep(priv_dev, USB_DIR_OUT);
>> +
>> +	writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
>> +	       &regs->ep_cfg);
>> +
>> +	writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN,
>> +	       &regs->ep_sts_en);
>> +
>> +	/* init ep in */
>> +	cdns3_select_ep(priv_dev, USB_DIR_IN);
>> +
>> +	writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
>> +	       &regs->ep_cfg);
>> +
>> +	writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, &regs->ep_sts_en);
>> +
>> +	cdns3_set_register_bit(&regs->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS);
>> +	cdns3_prepare_setup_packet(priv_dev);
>
>What happens if gadget.speed is USB_SPEED_SUPER, but was connected to a high-speed host?
>Are you updating the max_packet_size on connection done?

As above.
>
>> +}
>> +
>> +/**
>> + * cdns3_init_ep0 Initializes software endpoint 0 of gadget
>> + * @cdns3: extended gadget object
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +int cdns3_init_ep0(struct cdns3_device *priv_dev)
>> +{
>> +	struct cdns3_endpoint *ep0;
>> +
>> +	ep0 = devm_kzalloc(&priv_dev->dev, sizeof(struct cdns3_endpoint),
>> +			   GFP_KERNEL);
>> +
>> +	if (!ep0)
>> +		return -ENOMEM;
>> +
>> +	ep0->cdns3_dev = priv_dev;
>> +	sprintf(ep0->name, "ep0");
>> +
>> +	/* fill linux fields */
>> +	//TODO: implements cdns3_gadget_ep0_ops object
>> +	//ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
>> +	ep0->endpoint.maxburst = 1;
>> +	usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
>> +	ep0->endpoint.address = 0;
>> +	ep0->endpoint.caps.type_control = 1;
>> +	ep0->endpoint.caps.dir_in = 1;
>> +	ep0->endpoint.caps.dir_out = 1;
>> +	ep0->endpoint.name = ep0->name;
>> +	ep0->endpoint.desc = &cdns3_gadget_ep0_desc;
>> +	priv_dev->gadget.ep0 = &ep0->endpoint;
>> +	INIT_LIST_HEAD(&ep0->request_list);
>> +
>> +	return 0;
>> +}
>> diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h
>> new file mode 100644
>> index 000000000000..257e5e0eef31
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/gadget-export.h
>> @@ -0,0 +1,27 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Cadence USBSS DRD Driver -Gadget Export APIs
>> + *
>> + * Copyright (C) 2017 NXP
>> + *
>> + * Authors: Peter Chen <peter.chen@nxp.com>
>> + */
>> +#ifndef __LINUX_CDNS3_GADGET_EXPORT
>> +#define __LINUX_CDNS3_GADGET_EXPORT
>> +
>> +#ifdef CONFIG_USB_CDNS3_GADGET
>> +
>> +int cdns3_gadget_init(struct cdns3 *cdns);
>> +void cdns3_gadget_remove(struct cdns3 *cdns);
>> +#else
>> +
>> +static inline int cdns3_gadget_init(struct cdns3 *cdns)
>> +{
>> +	return -ENXIO;
>> +}
>> +
>> +static inline void cdns3_gadget_remove(struct cdns3 *cdns) { }
>> +
>> +#endif
>> +
>> +#endif /* __LINUX_CDNS3_GADGET_EXPORT */
>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>> new file mode 100644
>> index 000000000000..376b68b13d1b
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/gadget.c
>> @@ -0,0 +1,390 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver - gadget side.
>> + *
>> + * Copyright (C) 2018 Cadence Design Systems.
>> + * Copyright (C) 2017 NXP
>> + *
>> + * Authors: Pawel Jez <pjez@cadence.com>,
>> + *          Pawel Laszczak <pawell@cadence.com>
>> + *	    Peter Chen <peter.chen@nxp.com>
>> + */
>> +
>> +#include <linux/dma-mapping.h>
>> +#include <linux/usb/gadget.h>
>> +
>> +#include "core.h"
>> +#include "gadget-export.h"
>> +#include "gadget.h"
>> +
>> +/**
>> + * cdns3_set_register_bit - set bit in given register.
>> + * @ptr: address of device controller register to be read and changed
>> + * @mask: bits requested to set
>> + */
>> +void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
>> +{
>> +	mask = readl(ptr) | mask;
>> +	writel(mask, ptr);
>> +}
>
>static inline?
>I'd get rid of this function if possible. You are using it only once
>and it is easier to read code if setting the bitmask is done plainly
>instead of hiding it behind a function.

It will be used at least 6 times in the entire driver. 

>
>> +
>> +/**
>> + * select_ep - selects endpoint
>> + * @priv_dev:  extended gadget object
>> + * @ep: endpoint address
>> + */
>> +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
>> +{
>> +	if (priv_dev->selected_ep == ep)
>> +		return;
>
>I didn't understand the purpose of this function. You can only select the EP once?

Yes I can, but the endpoint is selected in many place in the driver and sometimes the same endpoint 
Is selected many times subsequently. 
Access to register can be slow, so this solution can improve performance. 
If I'm wrong please correct me. 
>
>> +
>> +	dev_dbg(&priv_dev->dev, "Ep sel: 0x%02x\n", ep);
>
>You should move to use trace-points instead of dev_dbg.

Yes, I know in next patch I will add trace-point
>
>> +	priv_dev->selected_ep = ep;
>> +	writel(ep, &priv_dev->regs->ep_sel);
>> +}
>> +
>> +/**
>> + * cdns3_irq_handler - irq line interrupt handler
>> + * @cdns: cdns3 instance
>> + *
>> + * Returns IRQ_HANDLED when interrupt raised by USBSS_DEV,
>> + * IRQ_NONE when interrupt raised by other device connected
>> + * to the irq line
>> + */
>> +static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>> +{
>> +	irqreturn_t ret = IRQ_NONE;
>> +	//TODO: implements this function
>> +	return ret;
>> +}
>> +
>> +static void cdns3_gadget_config(struct cdns3_device *priv_dev)
>> +{
>> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>> +
>> +	cdns3_ep0_config(priv_dev);
>> +
>> +	/* enable interrupts for endpoint 0 (in and out) */
>> +	writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, &regs->ep_ien);
>> +
>> +	/* enable generic interrupt*/
>> +	writel(USB_IEN_INIT, &regs->usb_ien);
>> +	writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, &regs->usb_conf);
>> +	writel(USB_CONF_DMULT, &regs->usb_conf);
>> +	writel(USB_CONF_DEVEN, &regs->usb_conf);
>
>If you are enabling interrupts in this patch you should handle them in the ISR.
>
>> +}
>> +
>> +/**
>> + * cdns3_init_ep Initializes software endpoints of gadget
>> + * @cdns3: extended gadget object
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +static int cdns3_init_ep(struct cdns3_device *priv_dev)
>> +{
>> +	u32 ep_enabled_reg, iso_ep_reg;
>> +	struct cdns3_endpoint *priv_ep;
>> +	int found_endpoints = 0;
>> +	int ep_dir, ep_number;
>> +	u32 ep_mask;
>> +	int i;
>> +
>> +	/* Read it from USB_CAP3 to USB_CAP5 */
>> +	ep_enabled_reg = readl(&priv_dev->regs->usb_cap3);
>> +	iso_ep_reg = readl(&priv_dev->regs->usb_cap4);
>> +
>> +	dev_dbg(&priv_dev->dev, "Initializing non-zero endpoints\n");
>> +
>> +	for (i = 0; i < USB_SS_ENDPOINTS_MAX_COUNT; i++) {
>
>CDNS3_USB_SS_ENDPOINTS_MAX_COUNT

Ok, consistence in naming is important. 
>
>> +		ep_number = (i / 2) + 1;
>> +		ep_dir = i % 2;
>> +		ep_mask = BIT((16 * ep_dir) + ep_number);
>> +
>> +		if (!(ep_enabled_reg & ep_mask))
>> +			continue;
>> +
>> +		priv_ep = devm_kzalloc(&priv_dev->dev, sizeof(*priv_ep),
>> +				       GFP_KERNEL);
>> +		if (!priv_ep)
>> +			return -ENOMEM;
>> +
>> +		/* set parent of endpoint object */
>> +		priv_ep->cdns3_dev = priv_dev;
>> +		priv_dev->eps[found_endpoints++] = priv_ep;
>> +
>> +		snprintf(priv_ep->name, sizeof(priv_ep->name), "ep%d%s",
>> +			 ep_number, !!ep_dir ? "in" : "out");
>> +		priv_ep->endpoint.name = priv_ep->name;
>> +
>> +		usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
>> +					   ENDPOINT_MAX_PACKET_LIMIT);
>
>CDNS3_ENDPOINT_MAX_PACKET_LIMIT
>
>> +		priv_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;
>
>CDNS3_ENDPOINT_MAX_STREAMS
>
>> +		//TODO: Add implementation of cdns3_gadget_ep_ops
>> +		//priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
>> +		if (ep_dir)
>> +			priv_ep->endpoint.caps.dir_in = 1;
>> +		else
>> +			priv_ep->endpoint.caps.dir_out = 1;
>> +
>> +		if (iso_ep_reg & ep_mask)
>> +			priv_ep->endpoint.caps.type_iso = 1;
>> +
>> +		priv_ep->endpoint.caps.type_bulk = 1;
>> +		priv_ep->endpoint.caps.type_int = 1;
>> +		priv_ep->endpoint.maxburst = CDNS3_EP_BUF_SIZE - 1;
>> +
>> +		priv_ep->flags = 0;
>> +
>> +		dev_info(&priv_dev->dev, "Initialized  %s support: %s %s\n",
>> +			 priv_ep->name,
>> +			 priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "",
>> +			 priv_ep->endpoint.caps.type_iso ? "ISO" : "");
>> +
>> +		list_add_tail(&priv_ep->endpoint.ep_list,
>> +			      &priv_dev->gadget.ep_list);
>> +		INIT_LIST_HEAD(&priv_ep->request_list);
>> +		INIT_LIST_HEAD(&priv_ep->ep_match_pending_list);
>> +	}
>> +
>> +	priv_dev->ep_nums = found_endpoints;
>> +	return 0;
>> +}
>> +
>> +static void cdns3_gadget_release(struct device *dev)
>> +{
>> +	struct cdns3_device *priv_dev;
>> +
>> +	priv_dev = container_of(dev, struct cdns3_device, dev);
>> +	kfree(priv_dev);
>> +}
>> +
>> +static int __cdns3_gadget_init(struct cdns3 *cdns)
>> +{
>> +	struct cdns3_device *priv_dev;
>> +	struct device *dev;
>> +	int ret;
>> +
>> +	priv_dev = kzalloc(sizeof(*priv_dev), GFP_KERNEL);
>> +	if (!priv_dev)
>> +		return -ENOMEM;
>> +
>> +	dev = &priv_dev->dev;
>> +	dev->release = cdns3_gadget_release;
>> +	dev->parent = cdns->dev;
>> +	dev_set_name(dev, "gadget-cdns3");
>> +	cdns->gadget_dev = dev;
>> +
>> +	priv_dev->sysdev = cdns->dev;
>> +	ret = device_register(dev);
>
>Why do you need to create this dummy device? you could just use cdns3->dev.

Peter do you have any suggestion ?

It's works as garbage collector during changing role.  In my opinion this is 
the only advantage.

>
>> +	if (ret)
>> +		goto err1;
>> +
>> +	priv_dev->regs = cdns->dev_regs;
>> +
>> +	/* fill gadget fields */
>> +	priv_dev->gadget.max_speed = USB_SPEED_SUPER;
>> +	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
>> +	//TODO: Add implementation of cdns3_gadget_ops
>> +	//priv_dev->gadget.ops = &cdns3_gadget_ops;
>> +	priv_dev->gadget.name = "usb-ss-gadget";
>> +	priv_dev->gadget.sg_supported = 1;
>> +	priv_dev->is_connected = 0;
>> +
>> +	spin_lock_init(&priv_dev->lock);
>> +
>> +	priv_dev->in_standby_mode = 1;
>> +
>> +	/* initialize endpoint container */
>> +	INIT_LIST_HEAD(&priv_dev->gadget.ep_list);
>> +	INIT_LIST_HEAD(&priv_dev->ep_match_list);
>> +
>> +	ret = cdns3_init_ep0(priv_dev);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to create endpoint 0\n");
>> +		ret = -ENOMEM;
>
>why not just use the ret as is.
>
>> +		goto err2;
>> +	}
>> +
>> +	ret = cdns3_init_ep(priv_dev);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to create non zero endpoints\n");
>> +		ret = -ENOMEM;
>
>here too

Yes, sure.
>
>> +		goto err2;
>> +	}
>> +
>> +	/* allocate memory for default endpoint TRB */
>> +	priv_dev->trb_ep0 = dma_alloc_coherent(priv_dev->sysdev, 24,
>
>what is 24?
>In error path you are using TRB_SIZE * 2. Should we be using that here too?

Yes we need TRB_SIZE * 2. The second is for DMULT mode. DMA stop working on incorrect TRB.  
>
>> +					       &priv_dev->trb_ep0_dma, GFP_DMA);
>> +	if (!priv_dev->trb_ep0) {
>> +		dev_err(dev, "Failed to allocate memory for ep0 TRB\n");
>> +		ret = -ENOMEM;
>> +		goto err2;
>> +	}
>> +
>> +	/* allocate memory for setup packet buffer */
>> +	priv_dev->setup = dma_alloc_coherent(priv_dev->sysdev, 8,
>
>is 8 enough for all use cases? What about vendor specific setup requests?

It's only for SETUP packet. It always has 8 bytes.  For data stage for vendor request 
buffer will be delivered by vendor gadget drivers. 
>
>> +					     &priv_dev->setup_dma, GFP_DMA);
>> +	if (!priv_dev->setup) {
>> +		dev_err(dev, "Failed to allocate memory for SETUP buffer\n");
>> +		ret = -ENOMEM;
>> +		goto err3;
>> +	}
>> +
>> +	dev_dbg(dev, "Device Controller version: %08x\n",
>> +		readl(&priv_dev->regs->usb_cap6));
>> +	dev_dbg(dev, "USB Capabilities:: %08x\n",
>> +		readl(&priv_dev->regs->usb_cap1));
>> +	dev_dbg(dev, "On-Chip memory cnfiguration: %08x\n",
>> +		readl(&priv_dev->regs->usb_cap2));
>> +
>> +	/* add USB gadget device */
>> +	ret = usb_add_gadget_udc(&priv_dev->dev, &priv_dev->gadget);
>> +	if (ret < 0) {
>> +		dev_err(dev, "Failed to register USB device controller\n");
>> +		goto err4;
>> +	}
>> +
>> +	priv_dev->zlp_buf = kzalloc(ENDPOINT_ZLP_BUF_SIZE, GFP_KERNEL);
>> +	if (!priv_dev->zlp_buf) {
>> +		ret = -ENOMEM;
>> +		goto err4;
>> +	}
>
>how about allocating zlp_buf before usb_add_gadget_udc()?

Yes, sure. It will be safer. 
>
>> +
>> +	return 0;
>> +err4:
>> +	dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup,
>> +			  priv_dev->setup_dma);
>> +err3:
>> +	dma_free_coherent(priv_dev->sysdev, TRB_SIZE * 2, priv_dev->trb_ep0,
>> +			  priv_dev->trb_ep0_dma);
>> +err2:
>> +	device_del(dev);
>> +err1:
>> +	put_device(dev);
>> +	cdns->gadget_dev = NULL;
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_remove: parent must call this to remove UDC
>> + *
>> + * cdns: cdns3 instance
>> + */
>> +void cdns3_gadget_remove(struct cdns3 *cdns)
>
>how about calling this cdns3_gadget_exit() to complememnt cdns3_gadget_init()

Function will be removed in next version. Code will be moved to cdns3_gadget_stop function

>
>> +{
>> +	struct cdns3_device *priv_dev;
>> +
>> +	if (!cdns->roles[CDNS3_ROLE_GADGET])
>> +		return;
>
>why this check? It will lead to an unbalanced exit() and future init().
>
Will be removed.
>> +
>> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
>> +	usb_del_gadget_udc(&priv_dev->gadget);
>> +	dma_free_coherent(priv_dev->sysdev, 8, priv_dev->setup,
>> +			  priv_dev->setup_dma);
>> +	dma_free_coherent(priv_dev->sysdev, TRB_SIZE * 2, priv_dev->trb_ep0,
>> +			  priv_dev->trb_ep0_dma);
>
>You need to free all memory allocation done in cdns3_init_ep0() and cdns3_init_ep()
>else we eat memory on every role switch if we plan on getting rid of the dummy gadget_dev.

Ok, 

>
>> +	device_unregister(cdns->gadget_dev);
>> +	cdns->gadget_dev = NULL;
>> +	kfree(priv_dev->zlp_buf);
>> +}
>> +
>> +static int cdns3_gadget_start(struct cdns3 *cdns)
>> +{
>> +	struct cdns3_device *priv_dev = container_of(cdns->gadget_dev,
>> +			struct cdns3_device, dev);
>> +	unsigned long flags;
>> +
>> +	pm_runtime_get_sync(cdns->dev);
>
>This is another reason why we shouldn't be creating a dummy gadget_dev.
>PM is tied to cdns->dev.
>
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	priv_dev->start_gadget = 1;
>> +
>> +	if (!priv_dev->gadget_driver) {
>> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +		return 0;
>> +	}
>> +
>> +	cdns3_gadget_config(priv_dev);
>> +	priv_dev->in_standby_mode = 0;
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return 0;
>> +}
>> +
>> +static void __cdns3_gadget_stop(struct cdns3 *cdns)
>> +{
>> +	struct cdns3_device *priv_dev;
>> +	unsigned long flags;
>> +
>> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
>> +
>> +	if (priv_dev->gadget_driver)
>> +		priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
>> +
>> +	usb_gadget_disconnect(&priv_dev->gadget);
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
>> +
>> +	/* disable interrupt for device */
>> +	writel(0, &priv_dev->regs->usb_ien);
>> +	writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
>> +	priv_dev->start_gadget = 0;
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +}
>> +
>> +static void cdns3_gadget_stop(struct cdns3 *cdns)
>> +{
>> +	if (cdns->role == CDNS3_ROLE_GADGET)
>> +		__cdns3_gadget_stop(cdns);
>
>Why worry about role here? That should be job of core/drd driver.

Yes, will be corrected.
>
>> +
>> +	pm_runtime_mark_last_busy(cdns->dev);
>> +	pm_runtime_put_autosuspend(cdns->dev);
>> +}
>> +
>> +static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup)
>> +{
>> +	__cdns3_gadget_stop(cdns);
>> +	return 0;
>> +}
>> +
>> +static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated)
>> +{
>> +	struct cdns3_device *priv_dev;
>> +	unsigned long flags;
>> +
>> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	priv_dev->start_gadget = 1;
>
>who is using this start_gadget flag?

It's look like guard protecting before access to register when device is disabled. 
It's no longer necessary in next version and will be removed.  When device role 
Is active then access to registers is enabled. 
>
>> +	if (!priv_dev->gadget_driver) {
>> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +		return 0;
>> +	}
>> +
>> +	cdns3_gadget_config(priv_dev);
>> +	priv_dev->in_standby_mode = 0;
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_init - initialize device structure
>> + *
>> + * cdns: cdns3 instance
>> + *
>> + * This function initializes the gadget.
>> + */
>> +int cdns3_gadget_init(struct cdns3 *cdns)
>> +{
>> +	struct cdns3_role_driver *rdrv;
>> +
>> +	rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
>> +	if (!rdrv)
>> +		return -ENOMEM;
>> +
>> +	rdrv->start	= cdns3_gadget_start;
>> +	rdrv->stop	= cdns3_gadget_stop;
>
>Why not use gadget_init()/gadget_exit() here? That sounds much cleaner
>as you won't have ISR's active after a role stop.

Without cdns3 prefix ? 
cdns3_gadget_exit has removed so we need cdns3_gadget_stop as non static function. 

So I can rename cdns3_gadget_stop  function to cdns3_gadget_exti, but cdn3_gadget_start is busy. 

cdn3_gadget_start is used only internally in this file so maybe she can be called  gadget_init or  __ cdn3_gadget_init

>
>> +	rdrv->suspend	= cdns3_gadget_suspend;
>> +	rdrv->resume	= cdns3_gadget_resume;
>> +	rdrv->irq	= cdns3_irq_handler_thread;
>> +	rdrv->name	= "gadget";
>> +	cdns->roles[CDNS3_ROLE_GADGET] = rdrv;
>> +	return __cdns3_gadget_init(cdns);
>> +}
>> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
>> index 75ca6214e79a..3b0d4d2e4831 100644
>> --- a/drivers/usb/cdns3/gadget.h
>> +++ b/drivers/usb/cdns3/gadget.h
>> @@ -1068,4 +1068,8 @@ struct cdns3_device {
>>  	struct usb_request		*pending_status_request;
>>  };
>>
>> +void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>> +int cdns3_init_ep0(struct cdns3_device *priv_dev);
>> +void cdns3_ep0_config(struct cdns3_device *priv_dev);
>> +void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>>  #endif /* __LINUX_CDNS3_GADGET */
>>
>
>cheers,
>-roger
>
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-11-28 12:22   ` Roger Quadros
@ 2018-12-01 11:11     ` Pawel Laszczak
  2018-12-03 10:19       ` Pawel Laszczak
  2018-12-10  2:12     ` Peter Chen
  1 sibling, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-01 11:11 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar,
	Felipe Balbi

Hi,

>> Patch adds implementation callback function defined in
>> usb_gadget_ops object.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/gadget.c | 249 ++++++++++++++++++++++++++++++++++++-
>>  1 file changed, 247 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>> index 376b68b13d1b..702a05faa664 100644
>> --- a/drivers/usb/cdns3/gadget.c
>> +++ b/drivers/usb/cdns3/gadget.c
>> @@ -17,6 +17,36 @@
>>  #include "gadget-export.h"
>>  #include "gadget.h"
>>
>> +/**
>> + * cdns3_handshake - spin reading  until handshake completes or fails
>> + * @ptr: address of device controller register to be read
>> + * @mask: bits to look at in result of read
>> + * @done: value of those bits when handshake succeeds
>> + * @usec: timeout in microseconds
>> + *
>> + * Returns negative errno, or zero on success
>> + *
>> + * Success happens when the "mask" bits have the specified value (hardware
>> + * handshake done). There are two failure modes: "usec" have passed (major
>> + * hardware flakeout), or the register reads as all-ones (hardware removed).
>> + */
>> +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
>> +{
>> +	u32	result;
>> +
>> +	do {
>> +		result = readl(ptr);
>> +		if (result == ~(u32)0)	/* card removed */
>> +			return -ENODEV;
>
>Is this applicable to all registers?
>What is meant by card removed? We're not connected to host?
>
>how does EP reset behave when there is no USB connection?
>

Function was adopted from XHCI driver (xhci_handshake). 
I think it's about PCI card.  If this happens driver should read 0xffffffff from any register. 
I need to confirm it with hardware team or test it. 
I remember that I had such situation some time ago. 
My testing platform consist of separate FPGA  board and PC. Both has  PCI adapter 
and are connected by means of special PCI cable.

>> +		result &= mask;
>> +		if (result == done)
>> +			return 0;
>> +		udelay(1);
>> +		usec--;
>> +	} while (usec > 0);
>> +	return -ETIMEDOUT;
>> +}
>> +
>>  /**
>>   * cdns3_set_register_bit - set bit in given register.
>>   * @ptr: address of device controller register to be read and changed
>> @@ -43,6 +73,25 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
>>  	writel(ep, &priv_dev->regs->ep_sel);
>>  }
>>
>> +static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
>> +{
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +
>> +	if (priv_ep->trb_pool) {
>> +		dma_free_coherent(priv_dev->sysdev,
>> +				  TRB_RIGN_SIZE,
>> +				  priv_ep->trb_pool, priv_ep->trb_pool_dma);
>> +		priv_ep->trb_pool = NULL;
>> +	}
>> +
>> +	if (priv_ep->aligned_buff) {
>> +		dma_free_coherent(priv_dev->sysdev, CDNS3_UNALIGNED_BUF_SIZE,
>> +				  priv_ep->aligned_buff,
>> +				  priv_ep->aligned_dma_addr);
>> +		priv_ep->aligned_buff = NULL;
>> +	}
>> +}
>> +
>>  /**
>>   * cdns3_irq_handler - irq line interrupt handler
>>   * @cdns: cdns3 instance
>> @@ -58,6 +107,114 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>>  	return ret;
>>  }
>>
>> +/* Find correct direction for HW endpoint according to description */
>> +static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
>> +				   struct cdns3_endpoint *priv_ep)
>> +{
>> +	return (priv_ep->endpoint.caps.dir_in && usb_endpoint_dir_in(desc)) ||
>> +	       (priv_ep->endpoint.caps.dir_out && usb_endpoint_dir_out(desc));
>> +}
>> +
>> +static struct cdns3_endpoint *cdns3_find_available_ss_ep(struct cdns3_device *priv_dev,
>> +							 struct usb_endpoint_descriptor *desc)
>
>why is this function called ss_ep? This doesn't seem like only for superspeed endpoints.

I will remove _ss_. 
>
>> +{
>> +	struct usb_ep *ep;
>> +	struct cdns3_endpoint *priv_ep;
>> +
>> +	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
>> +		unsigned long num;
>> +		int ret;
>> +		/* ep name pattern likes epXin or epXout */
>> +		char c[2] = {ep->name[2], '\0'};
>> +
>> +		ret = kstrtoul(c, 10, &num);
>> +		if (ret)
>> +			return ERR_PTR(ret);
>> +
>> +		priv_ep = ep_to_cdns3_ep(ep);
>> +		if (cdns3_ep_dir_is_correct(desc, priv_ep)) {
>> +			if (!(priv_ep->flags & EP_USED)) {
>> +				priv_ep->num  = num;
>> +				priv_ep->flags |= EP_USED;
>> +				return priv_ep;
>> +			}
>> +		}
>> +	}
>> +	return ERR_PTR(-ENOENT);
>> +}
>> +
>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>> +					    struct usb_endpoint_descriptor *desc,
>> +					    struct usb_ss_ep_comp_descriptor *comp_desc)
>> +{
>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> +	struct cdns3_endpoint *priv_ep;
>> +	unsigned long flags;
>> +
>> +	priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
>> +	if (IS_ERR(priv_ep)) {
>> +		dev_err(&priv_dev->dev, "no available ep\n");
>> +		return NULL;
>> +	}
>> +
>> +	dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	priv_ep->endpoint.desc = desc;
>> +	priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
>> +	priv_ep->type = usb_endpoint_type(desc);
>> +
>> +	list_add_tail(&priv_ep->ep_match_pending_list,
>> +		      &priv_dev->ep_match_list);
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return &priv_ep->endpoint;
>> +}
>
>Why do you need a custom match_ep?
>doesn't usb_ep_autoconfig suffice?

I need to test it but at first glance it  looks like usb_ep_autoconfig suffice.

>
>You can check if EP is claimed or not by checking the ep->claimed flag.
>
>> +
>> +/**
>> + * cdns3_gadget_get_frame Returns number of actual ITP frame
>> + * @gadget: gadget object
>> + *
>> + * Returns number of actual ITP frame
>> + */
>> +static int cdns3_gadget_get_frame(struct usb_gadget *gadget)
>> +{
>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> +
>> +	return readl(&priv_dev->regs->usb_iptn);
>> +}
>> +
>> +static int cdns3_gadget_wakeup(struct usb_gadget *gadget)
>> +{
>> +	return 0;
>> +}
>> +
>> +static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget,
>> +					int is_selfpowered)
>> +{
>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> +	unsigned long flags;
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	gadget->is_selfpowered = !!is_selfpowered;
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return 0;
>> +}
>> +
>> +static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on)
>> +{
>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> +
>> +	if (!priv_dev->start_gadget)
>> +		return 0;
>> +
>> +	if (is_on)
>> +		writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf);
>> +	else
>> +		writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
>> +
>> +	return 0;
>> +}
>> +
>>  static void cdns3_gadget_config(struct cdns3_device *priv_dev)
>>  {
>>  	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>> @@ -74,6 +231,95 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev)
>>  	writel(USB_CONF_DEVEN, &regs->usb_conf);
>>  }
>>
>> +/**
>> + * cdns3_gadget_udc_start Gadget start
>> + * @gadget: gadget object
>> + * @driver: driver which operates on this gadget
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +static int cdns3_gadget_udc_start(struct usb_gadget *gadget,
>> +				  struct usb_gadget_driver *driver)
>> +{
>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> +	unsigned long flags;
>> +
>> +	if (priv_dev->gadget_driver) {
>> +		dev_err(&priv_dev->dev, "%s is already bound to %s\n",
>> +			priv_dev->gadget.name,
>> +			priv_dev->gadget_driver->driver.name);
>> +		return -EBUSY;
>> +	}
>
>Not sure if this check is required. UDC core should be doing that.
>
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	priv_dev->gadget_driver = driver;
>> +	if (!priv_dev->start_gadget)
>> +		goto unlock;
>> +
>> +	cdns3_gadget_config(priv_dev);
>> +unlock:
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_udc_stop Stops gadget
>> + * @gadget: gadget object
>> + *
>> + * Returns 0
>> + */
>> +static int cdns3_gadget_udc_stop(struct usb_gadget *gadget)
>> +{
>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> +	struct cdns3_endpoint *priv_ep, *temp_ep;
>> +	u32 bEndpointAddress;
>> +	struct usb_ep *ep;
>> +	int ret = 0;
>> +	int i;
>> +
>> +	priv_dev->gadget_driver = NULL;
>> +	list_for_each_entry_safe(priv_ep, temp_ep, &priv_dev->ep_match_list,
>> +				 ep_match_pending_list) {
>> +		list_del(&priv_ep->ep_match_pending_list);
>> +		priv_ep->flags &= ~EP_USED;
>> +	}
>> +
>> +	priv_dev->onchip_mem_allocated_size = 0;
>> +	priv_dev->out_mem_is_allocated = 0;
>> +	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
>> +
>> +	for (i = 0; i < priv_dev->ep_nums ; i++)
>> +		cdns3_free_trb_pool(priv_dev->eps[i]);
>> +
>> +	if (!priv_dev->start_gadget)
>> +		return 0;
>
>This looks tricky. Why do we need this flag?

It will be removed in next version. It's no longer needed. 
>
>> +
>> +	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
>> +		priv_ep = ep_to_cdns3_ep(ep);
>> +		bEndpointAddress = priv_ep->num | priv_ep->dir;
>> +		cdns3_select_ep(priv_dev, bEndpointAddress);
>> +		writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
>> +		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
>> +				      EP_CMD_EPRST, 0, 100);
>> +	}
>> +
>> +	/* disable interrupt for device */
>> +	writel(0, &priv_dev->regs->usb_ien);
>> +	writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
>
>where are you requesting the interrupt? Looks like it should be done in
>udc_start() no?

Currently it is in initialization when the role is switched on. Interrupt is freeing 
during switching off role. So now the concept is the same as for host role. 
Whole device part is loaded and unloaded during changing role.. 
>
>> +
>> +	return ret;
>> +}
>
>Can we combine cdns3_gadget_udc_start() and cdns3_gadget_udc_start()
>with cdns3_gadget_start() and cdns3_gadget_stop() respectively so that
>cdns3_gadget_config() and cleanup() is done at one place.
>

Ok, probably it can be invoked only in cdns3_gadget_udc_start() and 
cdns3_gadget_udc_start(), but I have to test it. 
>> +
>> +static const struct usb_gadget_ops cdns3_gadget_ops = {
>> +	.get_frame = cdns3_gadget_get_frame,
>> +	.wakeup = cdns3_gadget_wakeup,
>> +	.set_selfpowered = cdns3_gadget_set_selfpowered,
>> +	.pullup = cdns3_gadget_pullup,
>> +	.udc_start = cdns3_gadget_udc_start,
>> +	.udc_stop = cdns3_gadget_udc_stop,
>> +	.match_ep = cdns3_gadget_match_ep,
>> +};
>> +
>>  /**
>>   * cdns3_init_ep Initializes software endpoints of gadget
>>   * @cdns3: extended gadget object
>> @@ -184,8 +430,7 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
>>  	/* fill gadget fields */
>>  	priv_dev->gadget.max_speed = USB_SPEED_SUPER;
>>  	priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
>> -	//TODO: Add implementation of cdns3_gadget_ops
>> -	//priv_dev->gadget.ops = &cdns3_gadget_ops;
>> +	priv_dev->gadget.ops = &cdns3_gadget_ops;
>>  	priv_dev->gadget.name = "usb-ss-gadget";
>>  	priv_dev->gadget.sg_supported = 1;
>>  	priv_dev->is_connected = 0;
>>
>
Cheers
Pawel

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

* RE: [RFC PATCH v2 09/15] usb:cdns3: EpX operations part of the API
  2018-11-28 12:46   ` Roger Quadros
@ 2018-12-01 13:30     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-01 13:30 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Felipe Balbi, Alan Douglas,
	jbergsagel, nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar

Hi
>> Patch implements callback functions for non-default endpoints
>> defined in usb_ep_ops object.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/ep0.c    |  18 ++
>>  drivers/usb/cdns3/gadget.c | 442 ++++++++++++++++++++++++++++++++++++-
>>  drivers/usb/cdns3/gadget.h |   3 +
>>  3 files changed, 461 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
>> index c08d02665f9d..ca1795467155 100644
>> --- a/drivers/usb/cdns3/ep0.c
>> +++ b/drivers/usb/cdns3/ep0.c
>> @@ -23,6 +23,24 @@ static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>>  	//TODO: Implements this function
>>  }
>>
>> +/**
>> + * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint
>> + * @ep: endpoint object
>> + *
>> + * Returns 0
>> + */
>> +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +
>> +	dev_dbg(&priv_dev->dev, "Wedge for %s\n", ep->name);
>> +	cdns3_gadget_ep_set_halt(ep, 1);
>> +	priv_ep->flags |= EP_WEDGE;
>> +
>> +	return 0;
>> +}
>> +
>>  /**
>>   * cdns3_ep0_config - Configures default endpoint
>>   * @priv_dev: extended gadget object
>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>> index 702a05faa664..1f2a434486dc 100644
>> --- a/drivers/usb/cdns3/gadget.c
>> +++ b/drivers/usb/cdns3/gadget.c
>> @@ -58,6 +58,19 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
>>  	writel(mask, ptr);
>>  }
>>
>> +/**
>> + * cdns3_next_request - returns next request from list
>> + * @list: list containing requests
>> + *
>> + * Returns request or NULL if no requests in list
>> + */
>> +struct usb_request *cdns3_next_request(struct list_head *list)
>> +{
>> +	if (list_empty(list))
>> +		return NULL;
>> +	return list_first_entry(list, struct usb_request, list);
>> +}
>> +
>>  /**
>>   * select_ep - selects endpoint
>>   * @priv_dev:  extended gadget object
>> @@ -73,6 +86,53 @@ void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep)
>>  	writel(ep, &priv_dev->regs->ep_sel);
>>  }
>>
>> +/**
>> + * cdns3_allocate_trb_pool - Allocates TRB's pool for selected endpoint
>> + * @priv_ep:  endpoint object
>> + *
>> + * Function will return 0 on success or -ENOMEM on allocation error
>> + */
>> +static int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep)
>> +{
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	struct cdns3_trb *link_trb;
>> +
>> +	if (!priv_ep->trb_pool) {
>> +		priv_ep->trb_pool = dma_zalloc_coherent(priv_dev->sysdev,
>> +							TRB_RIGN_SIZE,
>
>TRB_RING_SIZE
>
>> +							&priv_ep->trb_pool_dma,
>> +							GFP_DMA);
>> +		if (!priv_ep->trb_pool)
>> +			return -ENOMEM;
>> +	} else {
>> +		memset(priv_ep->trb_pool, 0, TRB_RIGN_SIZE);
>
>here too.
>
>> +	}
>> +
>> +	if (!priv_ep->aligned_buff) {
>> +		priv_ep->aligned_buff = dma_alloc_coherent(priv_dev->sysdev,
>> +							   CDNS3_UNALIGNED_BUF_SIZE,
>
>CDNS3_ALIGNED_BUF_SIZE
>
>> +							   &priv_ep->aligned_dma_addr,
>> +							   GFP_DMA);
>> +		if (!priv_ep->aligned_buff) {
>> +			dma_free_coherent(priv_dev->sysdev,
>> +					  TRB_RIGN_SIZE,
>> +					  priv_ep->trb_pool,
>> +					  priv_ep->trb_pool_dma);
>> +			priv_ep->trb_pool = NULL;
>> +
>> +			return -ENOMEM;
>> +		}
>> +	}
>> +
>> +	/* Initialize the last TRB as Link TRB */
>> +	link_trb = (priv_ep->trb_pool + TRBS_PER_SEGMENT - 1);
>> +	link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma);
>> +	link_trb->control = TRB_CYCLE | TRB_TYPE(TRB_LINK) |
>> +			    TRB_CHAIN | TRB_TOGGLE;
>> +
>> +	return 0;
>> +}
>> +
>>  static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
>>  {
>>  	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> @@ -92,6 +152,73 @@ static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep)
>>  	}
>>  }
>>
>> +/**
>> + * cdns3_data_flush - flush data at onchip buffer
>> + * @priv_ep: endpoint object
>> + *
>> + * Endpoint must be selected before call to this function
>> + *
>> + * Returns zero on success or negative value on failure
>> + */
>> +static int cdns3_data_flush(struct cdns3_endpoint *priv_ep)
>> +{
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +
>> +	writel(EP_CMD_DFLUSH, &priv_dev->regs->ep_cmd);
>> +
>> +	/* wait for DFLUSH cleared */
>> +	return cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 100);
>> +}
>> +
>> +/**
>> + * cdns3_ep_stall_flush - Stalls and flushes selected endpoint
>> + * @priv_ep: endpoint object
>> + *
>> + * Endpoint must be selected before call to this function
>> + */
>> +static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
>> +{
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +
>> +	writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL,
>> +	       &priv_dev->regs->ep_cmd);
>> +
>> +	/* wait for DFLUSH cleared */
>> +	cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 100);
>> +	priv_ep->flags |= EP_STALL;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_giveback - call struct usb_request's ->complete callback
>> + * @priv_ep: The endpoint to whom the request belongs to
>> + * @priv_req: The request we're giving back
>> + * @status: completion code for the request
>> + *
>> + * Must be called with controller's lock held and interrupts disabled. This
>> + * function will unmap @req and call its ->complete() callback to notify upper
>> + * layers that it has completed.
>> + */
>> +void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
>> +			   struct cdns3_request *priv_req,
>> +			   int status)
>> +{
>> +	//TODO: Implements this function.
>> +}
>> +
>> +/**
>> + * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware
>> + * @priv_ep: endpoint object
>> + *
>> + * Returns zero on success or negative value on failure
>> + */
>> +int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>> +			  struct usb_request *request)
>> +{
>> +	//TODO: Implements this function.
>> +
>> +	return 0;
>> +}
>> +
>>  /**
>>   * cdns3_irq_handler - irq line interrupt handler
>>   * @cdns: cdns3 instance
>> @@ -170,6 +297,318 @@ static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>>  	return &priv_ep->endpoint;
>>  }
>>
>> +/**
>> + * cdns3_gadget_ep_enable Enable endpoint
>> + * @ep: endpoint object
>> + * @desc: endpoint descriptor
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +static int cdns3_gadget_ep_enable(struct usb_ep *ep,
>> +				  const struct usb_endpoint_descriptor *desc)
>> +{
>> +	struct cdns3_endpoint *priv_ep;
>> +	struct cdns3_device *priv_dev;
>> +	unsigned long flags;
>> +	int ret;
>> +	u32 reg;
>> +
>> +	priv_ep = ep_to_cdns3_ep(ep);
>> +	priv_dev = priv_ep->cdns3_dev;
>> +
>> +	if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) {
>> +		dev_err(&priv_dev->dev, "usbss: invalid parameters\n");
>
>dev_dbg()?
>
>Gadget driver will be more verbose.
>
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (!desc->wMaxPacketSize) {
>> +		dev_err(&priv_dev->dev, "usbss: missing wMaxPacketSize\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (dev_WARN_ONCE(&priv_dev->dev, priv_ep->flags & EP_ENABLED,
>> +			  "%s is already enabled\n", priv_ep->name))
>> +		return 0;
>> +
>> +	ret = cdns3_allocate_trb_pool(priv_ep);
>> +	if (ret)
>> +		return ret;
>
>Why not allocate the TRB pool once for all endpoints at gadget init?
>
>you don't seem to be calling cdns3_allocate_trb_pool() in cdns3_gadget_ep_disable().

I allocate memory when endpoint is enabled and free in cdns3_gadget_udc_stop.
Memory is allocated only once and only for endpoint that are used  by function. 
This solution  allows to saves some memory. 

>> +
>> +	dev_dbg(&priv_dev->dev, "Enabling endpoint: %s\n", ep->name);
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	cdns3_select_ep(priv_dev, desc->bEndpointAddress);
>> +	writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
>> +
>> +	ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
>> +			      EP_CMD_CSTALL | EP_CMD_EPRST, 0, 100);
>> +
>> +	cdns3_set_register_bit(&priv_dev->regs->ep_cfg, EP_CFG_ENABLE);
>> +
>> +	ep->desc = desc;
>> +	priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALL);
>> +	priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR;
>> +	priv_ep->enqueue = 0;
>> +	priv_ep->dequeue = 0;
>> +	reg = readl(&priv_dev->regs->ep_sts);
>> +	priv_ep->pcs = !!EP_STS_CCS(reg);
>> +	priv_ep->ccs = !!EP_STS_CCS(reg);
>> +	/* one TRB is reserved for link TRB used in DMULT mode*/
>> +	priv_ep->free_trbs = TRBS_PER_SEGMENT - 1;
>> +
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep_disable Disable endpoint
>> + * @ep: endpoint object
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +static int cdns3_gadget_ep_disable(struct usb_ep *ep)
>> +{
>> +	struct cdns3_endpoint *priv_ep;
>> +	struct cdns3_device *priv_dev;
>> +	unsigned long flags;
>> +	int ret = 0;
>> +	struct usb_request *request;
>> +	u32 ep_cfg;
>> +
>> +	if (!ep) {
>> +		pr_debug("usbss: invalid parameters\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv_ep = ep_to_cdns3_ep(ep);
>> +	priv_dev = priv_ep->cdns3_dev;
>> +
>> +	if (dev_WARN_ONCE(&priv_dev->dev, !(priv_ep->flags & EP_ENABLED),
>> +			  "%s is already disabled\n", priv_ep->name))
>> +		return 0;
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	if (!priv_dev->start_gadget) {
>> +		dev_dbg(&priv_dev->dev,
>> +			"Disabling endpoint at disconnection: %s\n", ep->name);
>
>This flag is looking very tricky.
>What do you mean by "disabling at disconnection"?

start_gadget fag and this  fragment has been removed.

>
>> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +		return 0;
>
>EP is not yet disabled and we're returning 0. This will cause an unbalance.
>I'd avoid that flag altogether.
>
>> +	}
>> +
>> +	dev_dbg(&priv_dev->dev, "Disabling endpoint: %s\n", ep->name);
>> +
>> +	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
>> +	ret = cdns3_data_flush(priv_ep);
>> +	while (!list_empty(&priv_ep->request_list)) {
>> +		request = cdns3_next_request(&priv_ep->request_list);
>> +
>> +		cdns3_gadget_giveback(priv_ep, to_cdns3_request(request),
>> +				      -ESHUTDOWN);
>> +	}
>> +
>> +	ep_cfg = readl(&priv_dev->regs->ep_cfg);
>> +	ep_cfg &= ~EP_CFG_ENABLE;
>> +	writel(ep_cfg, &priv_dev->regs->ep_cfg);
>> +	ep->desc = NULL;
>> +	priv_ep->flags &= ~EP_ENABLED;
>> +
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep_alloc_request Allocates request
>> + * @ep: endpoint object associated with request
>> + * @gfp_flags: gfp flags
>> + *
>> + * Returns allocated request address, NULL on allocation error
>> + */
>> +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
>> +						  gfp_t gfp_flags)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_request *priv_req;
>> +
>> +	priv_req = kzalloc(sizeof(*priv_req), gfp_flags);
>> +	if (!priv_req)
>> +		return NULL;
>> +
>> +	priv_req->priv_ep = priv_ep;
>> +
>> +	return &priv_req->request;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep_free_request Free memory occupied by request
>> + * @ep: endpoint object associated with request
>> + * @request: request to free memory
>> + */
>> +void cdns3_gadget_ep_free_request(struct usb_ep *ep,
>> +				  struct usb_request *request)
>> +{
>> +	struct cdns3_request *priv_req = to_cdns3_request(request);
>> +
>> +	kfree(priv_req);
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep_queue Transfer data on endpoint
>> + * @ep: endpoint object
>> + * @request: request object
>> + * @gfp_flags: gfp flags
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +static int __cdns3_gadget_ep_queue(struct usb_ep *ep,
>> +				   struct usb_request *request,
>> +				   gfp_t gfp_flags)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	int ret = 0;
>> +
>> +	request->actual = 0;
>> +	request->status = -EINPROGRESS;
>> +
>> +	dev_dbg(&priv_dev->dev, "Queuing to endpoint: %s\n", priv_ep->name);
>> +
>> +	ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
>> +					    usb_endpoint_dir_in(ep->desc));
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (!cdns3_ep_run_transfer(priv_ep, request))
>> +		list_add_tail(&request->list, &priv_ep->request_list);
>
>how about catching the return value if cdns3_ep_run_transfer() fails?

Function has two incorrect situations that can occur. The first one is for request equal NULL
and the second is when endpoint hasn't sufficient free TRBs for transfer.
The first condition I will move to cdns3_gadget_ep_queue function. 

For second case, the good solution is to keep such request in software queue. 
Driver can send these latter when sufficient number of TRBs will be free in transfer ring. 
>> 
>> +	return ret;
>> +}
>> +
>> +static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
>> +				 gfp_t gfp_flags)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	struct usb_request *zlp_request;
>> +	unsigned long flags;
>> +	int ret;
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags);
>> +
>> +	if (ret == 0 && request->zero && request->length &&
>> +	    (request->length % ep->maxpacket == 0)) {
>> +		zlp_request = cdns3_gadget_ep_alloc_request(ep, GFP_ATOMIC);
>> +		zlp_request->buf = priv_dev->zlp_buf;
>> +		zlp_request->length = 0;
>> +
>> +		dev_dbg(&priv_dev->dev, "Queuing ZLP for endpoint: %s\n",
>> +			priv_ep->name);
>> +		ret = __cdns3_gadget_ep_queue(ep, zlp_request, gfp_flags);
>
>Who is going to free this zlp_request?

It's freeing in cdns3_gadget_giveback and it looks like:

	if (request->buf == priv_dev->zlp_buf)
		cdns3_gadget_ep_free_request(&priv_ep->endpoint, request);
 
>
>> +	}
>> +
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep_dequeue Remove request from transfer queue
>> + * @ep: endpoint object associated with request
>> + * @request: request object
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +int cdns3_gadget_ep_dequeue(struct usb_ep *ep,
>> +			    struct usb_request *request)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	struct usb_request *req, *req_temp;
>> +	unsigned long flags;
>> +	int ret = 0;
>> +
>> +	if (!ep || !request || !ep->desc)
>> +		return -EINVAL;
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	dev_dbg(&priv_dev->dev, "Dequeue from %s\n", ep->name);
>> +
>> +	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
>> +	if (priv_dev->start_gadget)
>> +		ret = cdns3_data_flush(priv_ep);
>> +
>> +	list_for_each_entry_safe(req, req_temp, &priv_ep->request_list, list) {
>> +		if (request == req) {
>> +			cdns3_gadget_giveback(priv_ep,
>> +					      to_cdns3_request(request),
>> +					      -ECONNRESET);
>> +			break;
>> +		}
>> +	}
>> +
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep_set_halt Sets/clears stall on selected endpoint
>> + * @ep: endpoint object to set/clear stall on
>> + * @value: 1 for set stall, 0 for clear stall
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	unsigned long flags;
>> +	int ret = 0;
>> +
>> +	if (!(priv_ep->flags & EP_ENABLED))
>> +		return -EPERM;
>> +
>> +	/* if actual transfer is pending defer setting stall on this endpoint */
>> +	if ((priv_ep->flags & EP_PENDING_REQUEST) && value) {
>> +		priv_ep->flags |= EP_STALL;
>> +		return 0;
>> +	}
>> +
>> +	dev_dbg(&priv_dev->dev, "Halt endpoint %s\n", priv_ep->name);
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>
>How about grabbing the lock before checking for priv->ep->flags?
Ok, I will do it.
>
>> +
>> +	cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress);
>> +	if (value) {
>> +		cdns3_ep_stall_flush(priv_ep);
>> +	} else {
>> +		priv_ep->flags &= ~EP_WEDGE;
>> +		writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
>> +
>> +		/* wait for EPRST cleared */
>> +		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
>> +				      EP_CMD_EPRST, 0, 100);
>
>if there was an error we shouldn't be clearing the EP_STALL right
>and leave the pending flag?
Yes, I will add such condition, but generally I do not expect such behavior. 
Additionally I will add dev_err(). 
>
>> +		priv_ep->flags &= ~EP_STALL;
>> +	}
>> +
>> +	priv_ep->flags &= ~EP_PENDING_REQUEST;
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +
>> +	return ret;
>> +}
>> +
>> +extern const struct usb_ep_ops cdns3_gadget_ep0_ops;
>> +
>> +static const struct usb_ep_ops cdns3_gadget_ep_ops = {
>> +	.enable = cdns3_gadget_ep_enable,
>> +	.disable = cdns3_gadget_ep_disable,
>> +	.alloc_request = cdns3_gadget_ep_alloc_request,
>> +	.free_request = cdns3_gadget_ep_free_request,
>> +	.queue = cdns3_gadget_ep_queue,
>> +	.dequeue = cdns3_gadget_ep_dequeue,
>> +	.set_halt = cdns3_gadget_ep_set_halt,
>> +	.set_wedge = cdns3_gadget_ep_set_wedge,
>> +};
>> +
>>  /**
>>   * cdns3_gadget_get_frame Returns number of actual ITP frame
>>   * @gadget: gadget object
>> @@ -365,8 +804,7 @@ static int cdns3_init_ep(struct cdns3_device *priv_dev)
>>  		usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
>>  					   ENDPOINT_MAX_PACKET_LIMIT);
>>  		priv_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS;
>> -		//TODO: Add implementation of cdns3_gadget_ep_ops
>> -		//priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
>> +		priv_ep->endpoint.ops = &cdns3_gadget_ep_ops;
>>  		if (ep_dir)
>>  			priv_ep->endpoint.caps.dir_in = 1;
>>  		else
>> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
>> index 3b0d4d2e4831..a4be288b34cb 100644
>> --- a/drivers/usb/cdns3/gadget.h
>> +++ b/drivers/usb/cdns3/gadget.h
>> @@ -1072,4 +1072,7 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>> +int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
>> +int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
>> +
>>  #endif /* __LINUX_CDNS3_GADGET */
>>
Cheers 
Pawel

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

* RE: [RFC PATCH v2 10/15] usb:cdns3: Ep0 operations part of the API
  2018-11-28 14:31   ` Roger Quadros
@ 2018-12-02 10:34     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-02 10:34 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar


>> Patch implements related to default endpoint callback functions
>> defined in usb_ep_ops object
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/ep0.c    | 191 ++++++++++++++++++++++++++++++++++++-
>>  drivers/usb/cdns3/gadget.c |   8 ++
>>  drivers/usb/cdns3/gadget.h |  10 ++
>>  3 files changed, 207 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
>> index ca1795467155..d05169e73631 100644
>> --- a/drivers/usb/cdns3/ep0.c
>> +++ b/drivers/usb/cdns3/ep0.c
>> @@ -18,11 +18,185 @@ static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
>>  	.bmAttributes =	USB_ENDPOINT_XFER_CONTROL,
>>  };
>>
>> +/**
>> + * cdns3_ep0_run_transfer - Do transfer on default endpoint hardware
>> + * @priv_dev: extended gadget object
>> + * @dma_addr: physical address where data is/will be stored
>> + * @length: data length
>> + * @erdy: set it to 1 when ERDY packet should be sent -
>> + *        exit from flow control state
>> + */
>> +static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
>> +				   dma_addr_t dma_addr,
>> +				   unsigned int length, int erdy)
>> +{
>> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>> +
>> +	priv_dev->trb_ep0->buffer = TRB_BUFFER(dma_addr);
>> +	priv_dev->trb_ep0->length = TRB_LEN(length);
>> +	priv_dev->trb_ep0->control = TRB_CYCLE | TRB_IOC | TRB_TYPE(TRB_NORMAL);
>> +
>> +	cdns3_select_ep(priv_dev,
>> +			priv_dev->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT);
>> +
>> +	writel(EP_STS_TRBERR, &regs->ep_sts);
>> +	writel(EP_TRADDR_TRADDR(priv_dev->trb_ep0_dma), &regs->ep_traddr);
>> +
>> +	dev_dbg(&priv_dev->dev, "//Ding Dong ep0%s\n",
>> +		priv_dev->ep0_data_dir ? "IN" : "OUT");
>> +
>> +	/* TRB should be prepared before starting transfer */
>> +	writel(EP_CMD_DRDY, &regs->ep_cmd);
>> +
>> +	if (erdy)
>> +		writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
>> +}
>> +
>>  static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>>  {
>>  	//TODO: Implements this function
>>  }
>>
>> +static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>
>Is this going to be used only for ep0?
>If yes then let's have ep0 in the name.
>If not then this function should be in gadget.c

It going to be used only in ep0.c file but general it's related to all non-control endpoints. 

The controller has one strange limitation, namely endpoints configuration in controller 
can be set only once. and this function inform control about this.

II think that, will be better to move this function to gadget.c then adding ep0 to name.
>
>> +{
>> +	struct cdns3_endpoint *priv_ep;
>> +	struct usb_request *request;
>> +	struct usb_ep *ep;
>> +	int result = 0;
>> +
>> +	if (priv_dev->hw_configured_flag)
>> +		return;
>> +
>> +	writel(USB_CONF_CFGSET, &priv_dev->regs->usb_conf);
>> +	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>> +
>> +	cdns3_set_register_bit(&priv_dev->regs->usb_conf,
>> +			       USB_CONF_U1EN | USB_CONF_U2EN);
>
>Shouldn't U1/U2 be enabled only if the USB_DEVICE_U1_ENABLE/USB_DEVICE_U2_ENABLE
>device request was received?

These bits only inform the controller that it should enter to U1/U2 only if host asks for such a transition.

Device to force transition to U1/U2 must use other bits. 

>> +
>> +	/* wait until configuration set */
>> +	result = cdns3_handshake(&priv_dev->regs->usb_sts,
>> +				 USB_STS_CFGSTS_MASK, 1, 100);
>> +
>> +	priv_dev->hw_configured_flag = 1;
>> +	cdns3_enable_l1(priv_dev, 1);
>> +
>> +	list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
>> +		if (ep->enabled) {
>> +			priv_ep = ep_to_cdns3_ep(ep);
>> +			request = cdns3_next_request(&priv_ep->request_list);
>> +			if (request)
>> +				cdns3_ep_run_transfer(priv_ep, request);
>
>why are you starting transfers in a function that is supposed to set hw configuration only.

Because we can't start transfer before configuring endpoints in hardware. 
Also we can't configure each endpoint separately  in cdns3_gadget_ep_enable because 
USB_CONF_CFGSET can be set only once. It's the reason why transfer are deferred. 
It is disadvantage of this controller witch causes some problems. 
>
>> +		}
>> +	}
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep0_enable
>> + * Function shouldn't be called by gadget driver,
>> + * endpoint 0 is allways active
>> + */
>> +static int cdns3_gadget_ep0_enable(struct usb_ep *ep,
>> +				   const struct usb_endpoint_descriptor *desc)
>> +{
>> +	return -EINVAL;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep0_disable
>> + * Function shouldn't be called by gadget driver,
>> + * endpoint 0 is allways active
>> + */
>> +static int cdns3_gadget_ep0_disable(struct usb_ep *ep)
>> +{
>> +	return -EINVAL;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep0_set_halt
>> + * @ep: pointer to endpoint zero object
>> + * @value: 1 for set stall, 0 for clear stall
>> + *
>> + * Returns 0
>> + */
>> +static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value)
>> +{
>> +	/* TODO */
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_gadget_ep0_queue Transfer data on endpoint zero
>> + * @ep: pointer to endpoint zero object
>> + * @request: pointer to request object
>> + * @gfp_flags: gfp flags
>> + *
>> + * Returns 0 on success, error code elsewhere
>> + */
>> +static int cdns3_gadget_ep0_queue(struct usb_ep *ep,
>> +				  struct usb_request *request,
>> +				  gfp_t gfp_flags)
>> +{
>> +	struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	unsigned long flags;
>> +	int erdy_sent = 0;
>> +	int ret = 0;
>> +
>> +	dev_dbg(&priv_dev->dev, "Queue to Ep0%s L: %d\n",
>> +		priv_dev->ep0_data_dir ? "IN" : "OUT",
>> +		request->length);
>> +
>> +	/* send STATUS stage */
>> +	if (request->length == 0 && request->zero == 0) {
>
>Is this check sufficient to know STATUS stage?
>It might be normal for vendor specific control request to have
>request->length = 0 and request->zero = 0.

It's sufficient.  
cdns3_gadget_ep0_queue is invoked by gadget driver only for DATA stage 
or deferred STATUS stage (e.g during SET_CONFIGURATION request). 
In DATA stage driver shall send a number of date specified in SETUP packet
and it should not be zero. 
If wLength in SETUP packet is 0 then we don't have DATA stage, only SETUP and STATUS stage

>
>
>> +		spin_lock_irqsave(&priv_dev->lock, flags);
>> +		cdns3_select_ep(priv_dev, 0x00);
>> +
>> +		erdy_sent = !priv_dev->hw_configured_flag;
>> +		cdns3_set_hw_configuration(priv_dev);
>
>What if we're still busy with DATA stage of previous ep0_queue?
>
>if (!list_empty(&priv_ep->request_list)) should be done as the first thing in this function.
>
The SETUP hast to be handled. If the previous one has not been finished then controller
driver must abort the previous one and start handling the new one.
I know, and I can't find code responsible for this behavior.
I will add some protection against such situation. 

>> +
>> +		if (!erdy_sent)
>> +			writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL,
>> +			       &priv_dev->regs->ep_cmd);
>> +
>> +		cdns3_prepare_setup_packet(priv_dev);
>> +		request->actual = 0;
>> +		priv_dev->status_completion_no_call = true;
>> +		priv_dev->pending_status_request = request;
>> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +
>> +		/*
>> +		 * Since there is no completion interrupt for status stage,
>> +		 * it needs to call ->completion in software after
>> +		 * ep0_queue is back.
>> +		 */
>> +		queue_work(system_freezable_wq, &priv_dev->pending_status_wq);
>> +		return 0;
>> +	}
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	if (!list_empty(&priv_ep->request_list)) {
>> +		dev_err(&priv_dev->dev,
>> +			"can't handle multiple requests for ep0\n");
>> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +		return -EOPNOTSUPP;
>
>-EBUSY?
>
>> +	}
>> +
>> +	ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
>> +					    priv_dev->ep0_data_dir);
>> +	if (ret) {
>> +		spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +		dev_err(&priv_dev->dev, "failed to map request\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv_dev->ep0_request = request;
>> +	list_add_tail(&request->list, &priv_ep->request_list);
>> +	cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1);
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>> +
>> +	return ret;
>> +}
>> +
>>  /**
>>   * cdns3_gadget_ep_set_wedge Set wedge on selected endpoint
>>   * @ep: endpoint object
>> @@ -41,6 +215,17 @@ int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
>>  	return 0;
>>  }
>>
>> +const struct usb_ep_ops cdns3_gadget_ep0_ops = {
>> +	.enable = cdns3_gadget_ep0_enable,
>> +	.disable = cdns3_gadget_ep0_disable,
>> +	.alloc_request = cdns3_gadget_ep_alloc_request,
>> +	.free_request = cdns3_gadget_ep_free_request,
>> +	.queue = cdns3_gadget_ep0_queue,
>> +	.dequeue = cdns3_gadget_ep_dequeue,
>> +	.set_halt = cdns3_gadget_ep0_set_halt,
>> +	.set_wedge = cdns3_gadget_ep_set_wedge,
>> +};
>> +
>>  /**
>>   * cdns3_ep0_config - Configures default endpoint
>>   * @priv_dev: extended gadget object
>> @@ -62,6 +247,9 @@ void cdns3_ep0_config(struct cdns3_device *priv_dev)
>>  		priv_dev->ep0_request = NULL;
>>  	}
>>
>> +	priv_dev->u1_allowed = 0;
>> +	priv_dev->u2_allowed = 0;
>> +
>
>where do you set these?

In function cdns3_ep0_feature_handle_device in patch 12. 
It was stupid  idea to split this driver into series of patches. 
I thought that it was  required for such big driver. 
The next will be posted as single patch. 
>
>>  	priv_dev->gadget.ep0->maxpacket = max_packet_size;
>>  	cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
>>
>> @@ -106,8 +294,7 @@ int cdns3_init_ep0(struct cdns3_device *priv_dev)
>>  	sprintf(ep0->name, "ep0");
>>
>>  	/* fill linux fields */
>> -	//TODO: implements cdns3_gadget_ep0_ops object
>> -	//ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
>> +	ep0->endpoint.ops = &cdns3_gadget_ep0_ops;
>>  	ep0->endpoint.maxburst = 1;
>>  	usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT);
>>  	ep0->endpoint.address = 0;
>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>> index 1f2a434486dc..c965da16c0c8 100644
>> --- a/drivers/usb/cdns3/gadget.c
>> +++ b/drivers/usb/cdns3/gadget.c
>> @@ -188,6 +188,14 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
>>  	priv_ep->flags |= EP_STALL;
>>  }
>>
>
>> +void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>
>Some comment might help as to what L1 is.

I even had to check what it meant.

From controller specification: 
"
After receiving Extended Token packet, the USBSS-DEV answers with:
- ACK handshake when accepts transition to the Sleep state (L1 mode is turned on by setting
L1EN in USB_CONF) or
- NYET handshake when is not ready to make transition to the Sleep state (L1 mode is turned
off (default) by setting L1DS in USB_CONF )
- STALL handshake when doesn't recognize requested link state (bLinkState value). The only
supported bLinkState value is "0001" (Sleep state).
If L1 mode is enabled, then the status bit 'L1ENS' in 'USB_STS' is asserted."

So I think that the name of function should look like: 
cdns3_allow_enable_l1

I will add comment to function and change the name.
>
>> +{
>> +	if (enable)
>> +		writel(USB_CONF_L1EN, &priv_dev->regs->usb_conf);
>> +	else
>> +		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
>> +}
>> +
>>  /**
>>   * cdns3_gadget_giveback - call struct usb_request's ->complete callback
>>   * @priv_ep: The endpoint to whom the request belongs to
>> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
>> index a4be288b34cb..224f6b830bc9 100644
>> --- a/drivers/usb/cdns3/gadget.h
>> +++ b/drivers/usb/cdns3/gadget.h
>> @@ -1068,11 +1068,21 @@ struct cdns3_device {
>>  	struct usb_request		*pending_status_request;
>>  };
>>
>> +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>> +void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
>> +struct usb_request *cdns3_next_request(struct list_head *list);
>> +int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>> +			  struct usb_request *request);
>>  int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
>>  int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
>> +struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
>> +						  gfp_t gfp_flags);
>> +void cdns3_gadget_ep_free_request(struct usb_ep *ep,
>> +				  struct usb_request *request);
>> +int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *request);
>>
>>  #endif /* __LINUX_CDNS3_GADGET */
>>
>
>cheers,
>-roger
>
>--
>Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality.
  2018-11-28 14:54   ` Roger Quadros
@ 2018-12-02 11:49     ` Pawel Laszczak
  2018-12-02 12:52       ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-02 11:49 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

Hi,

>> Patch adds set of generic functions used for handling interrupts
>> generated by controller. Interrupt related functions are divided
>> into three groups. The first is related to ep0 and is placed in ep0.c.
>> The second is responsible for non-default endpoints and is
>> implemented in gadget.c file. The last group is not related to
>> endpoints interrupts and is placed in gadget.c.
>> All groups have common entry point in cdns3_irq_handler_thread function.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/ep0.c    |  63 +++++++++++
>>  drivers/usb/cdns3/gadget.c | 224 ++++++++++++++++++++++++++++++++++++-
>>  drivers/usb/cdns3/gadget.h |   1 +
>>  3 files changed, 287 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
>> index d05169e73631..eb92fd234bd7 100644
>> --- a/drivers/usb/cdns3/ep0.c
>> +++ b/drivers/usb/cdns3/ep0.c
>> @@ -90,6 +90,69 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>>  	}
>>  }
>>
>> +static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
>> +{
>> +	//TODO: Implements this function
>> +}
>> +
>> +/**
>> + * cdns3_ep0_setup_phase - Handling setup USB requests
>> + * @priv_dev: extended gadget object
>> + */
>> +static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
>> +{
>> +	//TODO: Implements this function.
>> +}
>> +
>> +static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
>> +{
>> +	//TODO: Implements this function
>> +}
>> +
>> +/**
>> + * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0
>> + * @priv_dev: extended gadget object
>> + * @dir: 1 for IN direction, 0 for OUT direction
>> + */
>> +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir)
>> +{
>> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>> +	u32 ep_sts_reg;
>> +
>> +	cdns3_select_ep(priv_dev, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT));
>> +	ep_sts_reg = readl(&regs->ep_sts);
>> +
>> +	__pending_setup_status_handler(priv_dev);
>> +
>> +	if ((ep_sts_reg & EP_STS_SETUP) && dir == 0) {
>> +		struct usb_ctrlrequest *setup = priv_dev->setup;
>> +
>> +		writel(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
>
>instead you can just clear all events at the end of this function by
>	writel(ep_sts_reg, &regs->ep_sts);

But I can delete events that can appear during processing this function. 
I think that safer is to add this fragment at the beginning, below line 
ep_sts_reg = readl(&regs->ep_sts);


>> +
>> +		priv_dev->ep0_data_dir = setup->bRequestType & USB_DIR_IN;
>> +		cdns3_ep0_setup_phase(priv_dev);
>> +		ep_sts_reg &= ~(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP);
>
>Not required.
Why not ? I must clear at least EP_STS_IOC | EP_STS_ISP. 
I don't want to handle  the last condition in this function, of course if SETUP was reported. 

Of course I can also change the construction of function and then I could delete this statement. 
>
>> +	}
>> +
>> +	if (ep_sts_reg & EP_STS_TRBERR)
>> +		writel(EP_STS_TRBERR, &priv_dev->regs->ep_sts);
>
>Can be omitted.
>> +
>> +	if (ep_sts_reg & EP_STS_DESCMIS) {
>> +		writel(EP_STS_DESCMIS, &priv_dev->regs->ep_sts);
>
>This as well.
>> +
>> +		if (dir == 0 && !priv_dev->setup_pending) {
>> +			priv_dev->ep0_data_dir = 0;
>> +			cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
>> +					       8, 0);
>> +		}
>> +	}
>> +
>> +	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
>> +		writel(EP_STS_IOC, &priv_dev->regs->ep_sts);
>
>this write can be omitted.
>> +		cdns3_transfer_completed(priv_dev);
>> +	}
>
>here you can do
>	writel(ep_sts_reg, &regs->ep_sts);
>> +}
>> +
>>  /**
>>   * cdns3_gadget_ep0_enable
>>   * Function shouldn't be called by gadget driver,
>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>> index c965da16c0c8..309202474e57 100644
>> --- a/drivers/usb/cdns3/gadget.c
>> +++ b/drivers/usb/cdns3/gadget.c
>> @@ -58,6 +58,18 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
>>  	writel(mask, ptr);
>>  }
>>
>> +/**
>> + * cdns3_ep_reg_pos_to_index - Macro converts bit position of ep_ists register
>> + * to index of endpoint object in cdns3_device.eps[] container
>> + * @i: bit position of endpoint for which endpoint object is required
>> + *
>> + * Remember that endpoint container doesn't contain default endpoint
>> + */
>> +static u8 cdns3_ep_reg_pos_to_index(int i)
>> +{
>> +	return ((i / 16) + (((i % 16) - 2) * 2));
>> +}
>> +
>>  /**
>>   * cdns3_next_request - returns next request from list
>>   * @list: list containing requests
>> @@ -188,6 +200,21 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
>>  	priv_ep->flags |= EP_STALL;
>>  }
>>
>> +/**
>> + * cdns3_gadget_unconfig - reset device configuration
>> + * @priv_dev: extended gadget object
>> + */
>> +void cdns3_gadget_unconfig(struct cdns3_device *priv_dev)
>> +{
>> +	/* RESET CONFIGURATION */
>> +	writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf);
>> +
>> +	cdns3_enable_l1(priv_dev, 0);
>> +	priv_dev->hw_configured_flag = 0;
>> +	priv_dev->onchip_mem_allocated_size = 0;
>> +	priv_dev->out_mem_is_allocated = 0;
>> +}
>> +
>>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>>  {
>>  	if (enable)
>> @@ -196,6 +223,23 @@ void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>>  		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
>>  }
>>
>> +static enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev)
>> +{
>> +	u32 reg;
>> +
>> +	reg = readl(&priv_dev->regs->usb_sts);
>> +
>> +	if (DEV_SUPERSPEED(reg))
>> +		return USB_SPEED_SUPER;
>> +	else if (DEV_HIGHSPEED(reg))
>> +		return USB_SPEED_HIGH;
>> +	else if (DEV_FULLSPEED(reg))
>> +		return USB_SPEED_FULL;
>> +	else if (DEV_LOWSPEED(reg))
>> +		return USB_SPEED_LOW;
>> +	return USB_SPEED_UNKNOWN;
>> +}
>> +
>>  /**
>>   * cdns3_gadget_giveback - call struct usb_request's ->complete callback
>>   * @priv_ep: The endpoint to whom the request belongs to
>> @@ -221,12 +265,136 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
>>   */
>>  int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>>  			  struct usb_request *request)
>> +{
>> +	return 0;
>> +}
>> +
>> +static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
>> +				     struct cdns3_endpoint *priv_ep)
>>  {
>>  	//TODO: Implements this function.
>> +}
>> +
>> +/**
>> + * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint
>> + * @priv_ep: endpoint object
>> + *
>> + * Returns 0
>> + */
>> +static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep)
>> +{
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	struct cdns3_usb_regs __iomem *regs;
>> +	u32 ep_sts_reg;
>> +
>> +	regs = priv_dev->regs;
>> +
>> +	cdns3_select_ep(priv_dev, priv_ep->endpoint.address);
>> +	ep_sts_reg = readl(&regs->ep_sts);
>> +
>> +	if (ep_sts_reg & EP_STS_TRBERR)
>> +		writel(EP_STS_TRBERR, &regs->ep_sts);
>> +
>> +	if (ep_sts_reg & EP_STS_ISOERR)
>> +		writel(EP_STS_ISOERR, &regs->ep_sts);
>> +
>> +	if (ep_sts_reg & EP_STS_OUTSMM)
>> +		writel(EP_STS_OUTSMM, &regs->ep_sts);
>> +
>> +	if (ep_sts_reg & EP_STS_NRDY)
>> +		writel(EP_STS_NRDY, &regs->ep_sts);
>
>Why check for each bit when you are not doing anything. Instead at the end
>you could just do
>	writel(ep_sts_reg, &regs->ep_sts)
>to clear all pending events.

Some time ago I had in these conditions debug messages, but then 
I created the cdns3_decode_epx_irq and I removed them from there. 

I will remove them. 

>> +
>> +	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
>> +		writel(EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
>> +		cdns3_transfer_completed(priv_dev, priv_ep);
>> +	}
>> +
>> +	if (ep_sts_reg & EP_STS_DESCMIS)
>> +		writel(EP_STS_DESCMIS, &regs->ep_sts);
>>
>>  	return 0;
>>  }
>>
>> +/**
>> + * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device
>> + * @priv_dev: extended gadget object
>> + * @usb_ists: bitmap representation of device's reported interrupts
>> + * (usb_ists register value)
>> + */
>> +static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev,
>> +					      u32 usb_ists)
>> +{
>> +	struct cdns3_usb_regs __iomem *regs;
>> +	int speed = 0;
>> +
>> +	regs = priv_dev->regs;
>> +
>> +	/* Connection detected */
>> +	if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) {
>> +		writel(USB_ISTS_CON2I | USB_ISTS_CONI, &regs->usb_ists);
>> +		speed = cdns3_get_speed(priv_dev);
>> +
>> +		dev_dbg(&priv_dev->dev, "Connection detected at speed: %s %d\n",
>> +			usb_speed_string(speed), speed);
>> +
>> +		priv_dev->gadget.speed = speed;
>> +		priv_dev->is_connected = 1;
>> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED);
>> +		cdns3_ep0_config(priv_dev);
>> +	}
>> +
>> +	/* SS Disconnection detected */
>> +	if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) {
>> +		dev_dbg(&priv_dev->dev, "Disconnection detected\n");
>> +
>> +		writel(USB_ISTS_DIS2I | USB_ISTS_DISI, &regs->usb_ists);
>> +		if (priv_dev->gadget_driver &&
>> +		    priv_dev->gadget_driver->disconnect) {
>> +			spin_unlock(&priv_dev->lock);
>> +			priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
>> +			spin_lock(&priv_dev->lock);
>> +		}
>> +		priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
>> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED);
>> +		priv_dev->is_connected = 0;
>> +		cdns3_gadget_unconfig(priv_dev);
>> +	}
>
>What about non Super-Speed disconnects?

I connect 2 conditions and I forgot to change  the comment. 
USB_ISTS_DIS2I - HS/FS disconnection
USB_ISTS_DISI) - SS disconnection

>
>> +
>> +	if (usb_ists & USB_ISTS_L2ENTI) {
>> +		dev_dbg(&priv_dev->dev, "Device suspended\n");
>> +		writel(USB_ISTS_L2ENTI, &regs->usb_ists);
>> +	}
>> +
>> +	/* Exit from standby mode on L2 exit (Suspend in HS/FS or SS) */
>> +	if (usb_ists & USB_ISTS_L2EXTI) {
>> +		dev_dbg(&priv_dev->dev, "[Interrupt] L2 exit detected\n");
>> +		writel(USB_ISTS_L2EXTI, &regs->usb_ists);
>> +	}
>> +
>> +	/* Exit from standby mode on U3 exit (Suspend in HS/FS or SS). */
>> +	if (usb_ists & USB_ISTS_U3EXTI) {
>> +		dev_dbg(&priv_dev->dev, "U3 exit detected\n");
>> +		writel(USB_ISTS_U3EXTI, &regs->usb_ists);
>> +	}
>> +
>> +	/* resets cases */
>> +	if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) {
>> +		writel(USB_ISTS_U2RESI | USB_ISTS_UWRESI | USB_ISTS_UHRESI,
>> +		       &regs->usb_ists);
>> +
>> +		/*read again to check the actuall speed*/
>> +		speed = cdns3_get_speed(priv_dev);
>> +
>> +		dev_dbg(&priv_dev->dev, "Reset detected at speed: %s %d\n",
>> +			usb_speed_string(speed), speed);
>> +
>> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_DEFAULT);
>> +		priv_dev->gadget.speed = speed;
>> +		cdns3_gadget_unconfig(priv_dev);
>> +		cdns3_ep0_config(priv_dev);
>> +	}
>> +}
>> +
>>  /**
>>   * cdns3_irq_handler - irq line interrupt handler
>>   * @cdns: cdns3 instance
>> @@ -237,8 +405,62 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>>   */
>>  static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>>  {
>> +	struct cdns3_device *priv_dev;
>>  	irqreturn_t ret = IRQ_NONE;
>> -	//TODO: implements this function
>> +	unsigned long flags;
>> +	u32 reg;
>> +
>> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +
>> +	/* check USB device interrupt */
>> +	reg = readl(&priv_dev->regs->usb_ists);
>> +	if (reg) {
>> +		dev_dbg(&priv_dev->dev, "IRQ: usb_ists: %08X\n", reg);
>
>dev_dbg will be terribly slow to be useful. Tracepoints?

It will be moved to Tracepoint.
>
>> +		cdns3_check_usb_interrupt_proceed(priv_dev, reg);
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>> +	/* check endpoint interrupt */
>> +	reg = readl(&priv_dev->regs->ep_ists);
>> +	if (reg != 0) {
>> +		dev_dbg(&priv_dev->dev, "IRQ ep_ists: %08X\n", reg);
>> +	} else {
>> +		if (USB_STS_CFGSTS(readl(&priv_dev->regs->usb_sts)))
>> +			ret = IRQ_HANDLED;
>
>Why is this done. We don't seem to be handling anything here.
>Don't we need to clear the usb_sts?

This check CFGSTS bit. From controller spec:
Configuration status - 
1 - device is in the configured state
0 - device is not configured
This bit set during SET_CONFIGURATION
request means that status stage of this
request was finished successfully, thus device
configuration was finished successfully

And again, the setting related with setting endpoint configuration. 

The author did not mention anything about setting alternate setting
that require reconfiguring endpoints.

I think that this fragment protects against handling epx before setting configuration. 

I think that I can omit this fragment, but I need to test it with Command Verifier.

Regarding clearing usb_sts,  it's cleared but in other functions  invoked from this one. 
Maybe it could be better to pass to these other function content of usb_ists and do 
clearing in one place. It could limit access to registers and make the code more readable   

>
>> +		goto irqend;
>> +	}
>> +
>> +	/* handle default endpoint OUT */
>> +	if (reg & EP_ISTS_EP_OUT0) {
>> +		cdns3_check_ep0_interrupt_proceed(priv_dev, 0);
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>> +	/* handle default endpoint IN */
>> +	if (reg & EP_ISTS_EP_IN0) {
>> +		cdns3_check_ep0_interrupt_proceed(priv_dev, 1);
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>> +	/* check if interrupt from non default endpoint, if no exit */
>> +	reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0);
>> +	if (!reg)
>> +		goto irqend;
>> +
>> +	do {
>> +		unsigned int bit_pos = ffs(reg);
>> +		u32 bit_mask = 1 << (bit_pos - 1);
>> +		int index;
>> +
>> +		index = cdns3_ep_reg_pos_to_index(bit_pos);
>> +		cdns3_check_ep_interrupt_proceed(priv_dev->eps[index]);
>> +		reg &= ~bit_mask;
>> +		ret = IRQ_HANDLED;
>> +	} while (reg);
>> +
>> +irqend:
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>>  	return ret;
>>  }
>>
>> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
>> index 224f6b830bc9..8c2f363f9340 100644
>> --- a/drivers/usb/cdns3/gadget.h
>> +++ b/drivers/usb/cdns3/gadget.h
>> @@ -1072,6 +1072,7 @@ int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>> +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
>>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
>>  struct usb_request *cdns3_next_request(struct list_head *list);
>>

Thanks
Cheers,
Pawel

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

* RE: [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality.
  2018-12-02 11:49     ` Pawel Laszczak
@ 2018-12-02 12:52       ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-02 12:52 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

>Hi,
>
>>> Patch adds set of generic functions used for handling interrupts
>>> generated by controller. Interrupt related functions are divided
>>> into three groups. The first is related to ep0 and is placed in ep0.c.
>>> The second is responsible for non-default endpoints and is
>>> implemented in gadget.c file. The last group is not related to
>>> endpoints interrupts and is placed in gadget.c.
>>> All groups have common entry point in cdns3_irq_handler_thread function.
>>>
>>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>>> ---
>>>  drivers/usb/cdns3/ep0.c    |  63 +++++++++++
>>>  drivers/usb/cdns3/gadget.c | 224 ++++++++++++++++++++++++++++++++++++-
>>>  drivers/usb/cdns3/gadget.h |   1 +
>>>  3 files changed, 287 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
>>> index d05169e73631..eb92fd234bd7 100644
>>> --- a/drivers/usb/cdns3/ep0.c
>>> +++ b/drivers/usb/cdns3/ep0.c
>>> @@ -90,6 +90,69 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>>>  	}
>>>  }
>>>
>>> +static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
>>> +{
>>> +	//TODO: Implements this function
>>> +}
>>> +
>>> +/**
>>> + * cdns3_ep0_setup_phase - Handling setup USB requests
>>> + * @priv_dev: extended gadget object
>>> + */
>>> +static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
>>> +{
>>> +	//TODO: Implements this function.
>>> +}
>>> +
>>> +static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
>>> +{
>>> +	//TODO: Implements this function
>>> +}
>>> +
>>> +/**
>>> + * cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0
>>> + * @priv_dev: extended gadget object
>>> + * @dir: 1 for IN direction, 0 for OUT direction
>>> + */
>>> +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir)
>>> +{
>>> +	struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
>>> +	u32 ep_sts_reg;
>>> +
>>> +	cdns3_select_ep(priv_dev, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT));
>>> +	ep_sts_reg = readl(&regs->ep_sts);
>>> +
>>> +	__pending_setup_status_handler(priv_dev);
>>> +
>>> +	if ((ep_sts_reg & EP_STS_SETUP) && dir == 0) {
>>> +		struct usb_ctrlrequest *setup = priv_dev->setup;
>>> +
>>> +		writel(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
>>
>>instead you can just clear all events at the end of this function by
>>	writel(ep_sts_reg, &regs->ep_sts);
>
>But I can delete events that can appear during processing this function.
>I think that safer is to add this fragment at the beginning, below line
>ep_sts_reg = readl(&regs->ep_sts);
>
>
>>> +
>>> +		priv_dev->ep0_data_dir = setup->bRequestType & USB_DIR_IN;
>>> +		cdns3_ep0_setup_phase(priv_dev);
>>> +		ep_sts_reg &= ~(EP_STS_SETUP | EP_STS_IOC | EP_STS_ISP);
>>
>>Not required.
>Why not ? I must clear at least EP_STS_IOC | EP_STS_ISP.
>I don't want to handle  the last condition in this function, of course if SETUP was reported.
>
>Of course I can also change the construction of function and then I could delete this statement.
>>
>>> +	}
>>> +
>>> +	if (ep_sts_reg & EP_STS_TRBERR)
>>> +		writel(EP_STS_TRBERR, &priv_dev->regs->ep_sts);
>>
>>Can be omitted.
>>> +
>>> +	if (ep_sts_reg & EP_STS_DESCMIS) {
>>> +		writel(EP_STS_DESCMIS, &priv_dev->regs->ep_sts);
>>
>>This as well.
>>> +
>>> +		if (dir == 0 && !priv_dev->setup_pending) {
>>> +			priv_dev->ep0_data_dir = 0;
>>> +			cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
>>> +					       8, 0);
>>> +		}
>>> +	}
>>> +
>>> +	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
>>> +		writel(EP_STS_IOC, &priv_dev->regs->ep_sts);
>>
>>this write can be omitted.
>>> +		cdns3_transfer_completed(priv_dev);
>>> +	}
>>
>>here you can do
>>	writel(ep_sts_reg, &regs->ep_sts);
>>> +}
>>> +
>>>  /**
>>>   * cdns3_gadget_ep0_enable
>>>   * Function shouldn't be called by gadget driver,
>>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>>> index c965da16c0c8..309202474e57 100644
>>> --- a/drivers/usb/cdns3/gadget.c
>>> +++ b/drivers/usb/cdns3/gadget.c
>>> @@ -58,6 +58,18 @@ void cdns3_set_register_bit(void __iomem *ptr, u32 mask)
>>>  	writel(mask, ptr);
>>>  }
>>>
>>> +/**
>>> + * cdns3_ep_reg_pos_to_index - Macro converts bit position of ep_ists register
>>> + * to index of endpoint object in cdns3_device.eps[] container
>>> + * @i: bit position of endpoint for which endpoint object is required
>>> + *
>>> + * Remember that endpoint container doesn't contain default endpoint
>>> + */
>>> +static u8 cdns3_ep_reg_pos_to_index(int i)
>>> +{
>>> +	return ((i / 16) + (((i % 16) - 2) * 2));
>>> +}
>>> +
>>>  /**
>>>   * cdns3_next_request - returns next request from list
>>>   * @list: list containing requests
>>> @@ -188,6 +200,21 @@ static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep)
>>>  	priv_ep->flags |= EP_STALL;
>>>  }
>>>
>>> +/**
>>> + * cdns3_gadget_unconfig - reset device configuration
>>> + * @priv_dev: extended gadget object
>>> + */
>>> +void cdns3_gadget_unconfig(struct cdns3_device *priv_dev)
>>> +{
>>> +	/* RESET CONFIGURATION */
>>> +	writel(USB_CONF_CFGRST, &priv_dev->regs->usb_conf);
>>> +
>>> +	cdns3_enable_l1(priv_dev, 0);
>>> +	priv_dev->hw_configured_flag = 0;
>>> +	priv_dev->onchip_mem_allocated_size = 0;
>>> +	priv_dev->out_mem_is_allocated = 0;
>>> +}
>>> +
>>>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>>>  {
>>>  	if (enable)
>>> @@ -196,6 +223,23 @@ void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable)
>>>  		writel(USB_CONF_L1DS, &priv_dev->regs->usb_conf);
>>>  }
>>>
>>> +static enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev)
>>> +{
>>> +	u32 reg;
>>> +
>>> +	reg = readl(&priv_dev->regs->usb_sts);
>>> +
>>> +	if (DEV_SUPERSPEED(reg))
>>> +		return USB_SPEED_SUPER;
>>> +	else if (DEV_HIGHSPEED(reg))
>>> +		return USB_SPEED_HIGH;
>>> +	else if (DEV_FULLSPEED(reg))
>>> +		return USB_SPEED_FULL;
>>> +	else if (DEV_LOWSPEED(reg))
>>> +		return USB_SPEED_LOW;
>>> +	return USB_SPEED_UNKNOWN;
>>> +}
>>> +
>>>  /**
>>>   * cdns3_gadget_giveback - call struct usb_request's ->complete callback
>>>   * @priv_ep: The endpoint to whom the request belongs to
>>> @@ -221,12 +265,136 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep,
>>>   */
>>>  int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>>>  			  struct usb_request *request)
>>> +{
>>> +	return 0;
>>> +}
>>> +
>>> +static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
>>> +				     struct cdns3_endpoint *priv_ep)
>>>  {
>>>  	//TODO: Implements this function.
>>> +}
>>> +
>>> +/**
>>> + * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint
>>> + * @priv_ep: endpoint object
>>> + *
>>> + * Returns 0
>>> + */
>>> +static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep)
>>> +{
>>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>>> +	struct cdns3_usb_regs __iomem *regs;
>>> +	u32 ep_sts_reg;
>>> +
>>> +	regs = priv_dev->regs;
>>> +
>>> +	cdns3_select_ep(priv_dev, priv_ep->endpoint.address);
>>> +	ep_sts_reg = readl(&regs->ep_sts);
>>> +
>>> +	if (ep_sts_reg & EP_STS_TRBERR)
>>> +		writel(EP_STS_TRBERR, &regs->ep_sts);
>>> +
>>> +	if (ep_sts_reg & EP_STS_ISOERR)
>>> +		writel(EP_STS_ISOERR, &regs->ep_sts);
>>> +
>>> +	if (ep_sts_reg & EP_STS_OUTSMM)
>>> +		writel(EP_STS_OUTSMM, &regs->ep_sts);
>>> +
>>> +	if (ep_sts_reg & EP_STS_NRDY)
>>> +		writel(EP_STS_NRDY, &regs->ep_sts);
>>
>>Why check for each bit when you are not doing anything. Instead at the end
>>you could just do
>>	writel(ep_sts_reg, &regs->ep_sts)
>>to clear all pending events.
>
>Some time ago I had in these conditions debug messages, but then
>I created the cdns3_decode_epx_irq and I removed them from there.
>
>I will remove them.
>
>>> +
>>> +	if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
>>> +		writel(EP_STS_IOC | EP_STS_ISP, &regs->ep_sts);
>>> +		cdns3_transfer_completed(priv_dev, priv_ep);
>>> +	}
>>> +
>>> +	if (ep_sts_reg & EP_STS_DESCMIS)
>>> +		writel(EP_STS_DESCMIS, &regs->ep_sts);
>>>
>>>  	return 0;
>>>  }
>>>
>>> +/**
>>> + * cdns3_check_usb_interrupt_proceed - Processes interrupt related to device
>>> + * @priv_dev: extended gadget object
>>> + * @usb_ists: bitmap representation of device's reported interrupts
>>> + * (usb_ists register value)
>>> + */
>>> +static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev,
>>> +					      u32 usb_ists)
>>> +{
>>> +	struct cdns3_usb_regs __iomem *regs;
>>> +	int speed = 0;
>>> +
>>> +	regs = priv_dev->regs;
>>> +
>>> +	/* Connection detected */
>>> +	if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) {
>>> +		writel(USB_ISTS_CON2I | USB_ISTS_CONI, &regs->usb_ists);
>>> +		speed = cdns3_get_speed(priv_dev);
>>> +
>>> +		dev_dbg(&priv_dev->dev, "Connection detected at speed: %s %d\n",
>>> +			usb_speed_string(speed), speed);
>>> +
>>> +		priv_dev->gadget.speed = speed;
>>> +		priv_dev->is_connected = 1;
>>> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_POWERED);
>>> +		cdns3_ep0_config(priv_dev);
>>> +	}
>>> +
>>> +	/* SS Disconnection detected */
>>> +	if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) {
>>> +		dev_dbg(&priv_dev->dev, "Disconnection detected\n");
>>> +
>>> +		writel(USB_ISTS_DIS2I | USB_ISTS_DISI, &regs->usb_ists);
>>> +		if (priv_dev->gadget_driver &&
>>> +		    priv_dev->gadget_driver->disconnect) {
>>> +			spin_unlock(&priv_dev->lock);
>>> +			priv_dev->gadget_driver->disconnect(&priv_dev->gadget);
>>> +			spin_lock(&priv_dev->lock);
>>> +		}
>>> +		priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
>>> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED);
>>> +		priv_dev->is_connected = 0;
>>> +		cdns3_gadget_unconfig(priv_dev);
>>> +	}
>>
>>What about non Super-Speed disconnects?
>
>I connect 2 conditions and I forgot to change  the comment.
>USB_ISTS_DIS2I - HS/FS disconnection
>USB_ISTS_DISI) - SS disconnection
>
>>
>>> +
>>> +	if (usb_ists & USB_ISTS_L2ENTI) {
>>> +		dev_dbg(&priv_dev->dev, "Device suspended\n");
>>> +		writel(USB_ISTS_L2ENTI, &regs->usb_ists);
>>> +	}
>>> +
>>> +	/* Exit from standby mode on L2 exit (Suspend in HS/FS or SS) */
>>> +	if (usb_ists & USB_ISTS_L2EXTI) {
>>> +		dev_dbg(&priv_dev->dev, "[Interrupt] L2 exit detected\n");
>>> +		writel(USB_ISTS_L2EXTI, &regs->usb_ists);
>>> +	}
>>> +
>>> +	/* Exit from standby mode on U3 exit (Suspend in HS/FS or SS). */
>>> +	if (usb_ists & USB_ISTS_U3EXTI) {
>>> +		dev_dbg(&priv_dev->dev, "U3 exit detected\n");
>>> +		writel(USB_ISTS_U3EXTI, &regs->usb_ists);
>>> +	}
>>> +
>>> +	/* resets cases */
>>> +	if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) {
>>> +		writel(USB_ISTS_U2RESI | USB_ISTS_UWRESI | USB_ISTS_UHRESI,
>>> +		       &regs->usb_ists);
>>> +
>>> +		/*read again to check the actuall speed*/
>>> +		speed = cdns3_get_speed(priv_dev);
>>> +
>>> +		dev_dbg(&priv_dev->dev, "Reset detected at speed: %s %d\n",
>>> +			usb_speed_string(speed), speed);
>>> +
>>> +		usb_gadget_set_state(&priv_dev->gadget, USB_STATE_DEFAULT);
>>> +		priv_dev->gadget.speed = speed;
>>> +		cdns3_gadget_unconfig(priv_dev);
>>> +		cdns3_ep0_config(priv_dev);
>>> +	}
>>> +}
>>> +
>>>  /**
>>>   * cdns3_irq_handler - irq line interrupt handler
>>>   * @cdns: cdns3 instance
>>> @@ -237,8 +405,62 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>>>   */
>>>  static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>>>  {
>>> +	struct cdns3_device *priv_dev;
>>>  	irqreturn_t ret = IRQ_NONE;
>>> -	//TODO: implements this function
>>> +	unsigned long flags;
>>> +	u32 reg;
>>> +
>>> +	priv_dev = container_of(cdns->gadget_dev, struct cdns3_device, dev);
>>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>>> +
>>> +	/* check USB device interrupt */
>>> +	reg = readl(&priv_dev->regs->usb_ists);
>>> +	if (reg) {
>>> +		dev_dbg(&priv_dev->dev, "IRQ: usb_ists: %08X\n", reg);
>>
>>dev_dbg will be terribly slow to be useful. Tracepoints?
>
>It will be moved to Tracepoint.
>>
>>> +		cdns3_check_usb_interrupt_proceed(priv_dev, reg);
>>> +		ret = IRQ_HANDLED;
>>> +	}
>>> +
>>> +	/* check endpoint interrupt */
>>> +	reg = readl(&priv_dev->regs->ep_ists);
>>> +	if (reg != 0) {
>>> +		dev_dbg(&priv_dev->dev, "IRQ ep_ists: %08X\n", reg);
>>> +	} else {
>>> +		if (USB_STS_CFGSTS(readl(&priv_dev->regs->usb_sts)))
>>> +			ret = IRQ_HANDLED;
>>
>>Why is this done. We don't seem to be handling anything here.
>>Don't we need to clear the usb_sts?
>
>This check CFGSTS bit. From controller spec:
>Configuration status -
>1 - device is in the configured state
>0 - device is not configured
>This bit set during SET_CONFIGURATION
>request means that status stage of this
>request was finished successfully, thus device
>configuration was finished successfully
>
>And again, the setting related with setting endpoint configuration.
>
>The author did not mention anything about setting alternate setting
>that require reconfiguring endpoints.
>
>I think that this fragment protects against handling epx before setting configuration.
>
>I think that I can omit this fragment, but I need to test it with Command Verifier.
>
>Regarding clearing usb_sts,  it's cleared but in other functions  invoked from this one.
>Maybe it could be better to pass to these other function content of usb_ists and do
>clearing in one place. It could limit access to registers and make the code more readable

Sorry, I am little confused. 
We have ep_sts, usb_ists and  ep_ists 
usb_ists - must be cleared by software 
ep_ists -  we don't have to clear this register. It's RO. I think that clearing it by software could 
 	   cause some racing. 
>>
>>> +		goto irqend;
>>> +	}
>>> +
>>> +	/* handle default endpoint OUT */
>>> +	if (reg & EP_ISTS_EP_OUT0) {
>>> +		cdns3_check_ep0_interrupt_proceed(priv_dev, 0);
>>> +		ret = IRQ_HANDLED;
>>> +	}
>>> +
>>> +	/* handle default endpoint IN */
>>> +	if (reg & EP_ISTS_EP_IN0) {
>>> +		cdns3_check_ep0_interrupt_proceed(priv_dev, 1);
>>> +		ret = IRQ_HANDLED;
>>> +	}
>>> +
>>> +	/* check if interrupt from non default endpoint, if no exit */
>>> +	reg &= ~(EP_ISTS_EP_OUT0 | EP_ISTS_EP_IN0);
>>> +	if (!reg)
>>> +		goto irqend;
>>> +
>>> +	do {
>>> +		unsigned int bit_pos = ffs(reg);
>>> +		u32 bit_mask = 1 << (bit_pos - 1);
>>> +		int index;
>>> +
>>> +		index = cdns3_ep_reg_pos_to_index(bit_pos);
>>> +		cdns3_check_ep_interrupt_proceed(priv_dev->eps[index]);
>>> +		reg &= ~bit_mask;
>>> +		ret = IRQ_HANDLED;
>>> +	} while (reg);
>>> +
>>> +irqend:
>>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>>>  	return ret;
>>>  }
>>>
>>> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
>>> index 224f6b830bc9..8c2f363f9340 100644
>>> --- a/drivers/usb/cdns3/gadget.h
>>> +++ b/drivers/usb/cdns3/gadget.h
>>> @@ -1072,6 +1072,7 @@ int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>>>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>>>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>>>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>>> +void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
>>>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>>>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
>>>  struct usb_request *cdns3_next_request(struct list_head *list);
>>>
>
>Thanks
>Cheers,
>Pawel

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

* RE: [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function.
  2018-11-28 15:50   ` Roger Quadros
@ 2018-12-02 16:39     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-02 16:39 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar

>On 18/11/18 12:09, Pawel Laszczak wrote:
>> Patch implements a set of function related to enumeration process.
>> Some standard requests are handled on controller driver level and
>> other are delegated to gadget core driver.
>> All class requests are delegated to gadget core driver.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/ep0.c    | 491 ++++++++++++++++++++++++++++++++++++-
>>  drivers/usb/cdns3/gadget.c | 119 +++++++++
>>  drivers/usb/cdns3/gadget.h |   4 +
>>  3 files changed, 610 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c
>> index eb92fd234bd7..6f33d98f7684 100644
>> --- a/drivers/usb/cdns3/ep0.c
>> +++ b/drivers/usb/cdns3/ep0.c
>> @@ -10,6 +10,7 @@
>>   *	    Peter Chen <peter.chen@nxp.com>
>>   */
>>
>> +#include <linux/usb/composite.h>
>>  #include "gadget.h"
>>
>>  static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
>> @@ -52,9 +53,31 @@ static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
>>  		writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
>>  }
>>
>> +/**
>> + * cdns3_ep0_delegate_req - Returns status of handling setup packet
>> + * Setup is handled by gadget driver
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns zero on success or negative value on failure
>> + */
>> +static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev,
>> +				  struct usb_ctrlrequest *ctrl_req)
>> +{
>> +	int ret;
>> +
>> +	spin_unlock(&priv_dev->lock);
>> +	priv_dev->setup_pending = 1;
>> +	ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req);
>> +	priv_dev->setup_pending = 0;
>
>Why is setup_pending flag being set and cleared?

This flag is checking during handling DESCMISS interrupt. If this flag is set 
then driver not start DMA for SETUP packet waiting in on-chip buffer.
>
>> +	spin_lock(&priv_dev->lock);
>> +	return ret;
>> +}
>> +
>>  static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
>>  {
>> -	//TODO: Implements this function
>> +	priv_dev->ep0_data_dir = 0;
>> +	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 8, 0);
>
>why hardcode to 8?
>Don't vendor specific requests have different lengths?

SETUP packet always has 8 bytes. 
I will change this to sizeof(struct struct usb_ctrlrequest). It says more.
>
>>  }
>>
>>  static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>> @@ -90,9 +113,431 @@ static void cdns3_set_hw_configuration(struct cdns3_device *priv_dev)
>>  	}
>>  }
>>
>> +/**
>> + * cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns 0 if success, 0x7FFF on deferred status stage, error code on error
>
>what is this magic number 0x7fff?

We will have USB_GADGET_DELAYED_STATUS instead 0x7FFF;
>
>> + */
>> +static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev,
>> +					   struct usb_ctrlrequest *ctrl_req)
>> +{
>> +	enum usb_device_state device_state = priv_dev->gadget.state;
>> +	struct cdns3_endpoint *priv_ep, *temp_ep;
>> +	u32 config = le16_to_cpu(ctrl_req->wValue);
>> +	int result = 0;
>> +
>> +	switch (device_state) {
>> +	case USB_STATE_ADDRESS:
>> +		/* Configure non-control EPs */
>> +		list_for_each_entry_safe(priv_ep, temp_ep,
>> +					 &priv_dev->ep_match_list,
>> +					 ep_match_pending_list)
>> +			cdns3_ep_config(priv_ep);
>
>Why configure here? They should be configured at ep_enable. no?
>And you don't need to maintain a ep_match/pending_list.

It's a little tricky. 
We need to configure all endpoint on SET_CONFIGURATION request. 
After reset we can set only once CFGSET bit in usb_conf register.
We don't need to enable endpoint, but we need set all other parameters.   
Not all endpoints are enabled before SET_CONFIGURATION, but
most of them, or even all are claimed during loading function by means 
of usb_ep_autoconfig. 

After setting CFGSET bit we can't change configuration of any endpoint. 
We can only enable or disable them. 
>> +
>> +		result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
>> +
>> +		if (result)
>> +			return result;
>> +
>> +		if (config) {
>
>What if result is USB_GADGET_DELAYED_STATUS?
It's handled be cdns3_ep0_setup_phase when this function return USB_GADGET_DELAYED_STATUS

>
>> +			cdns3_set_hw_configuration(priv_dev);
>
>usb_gadget_set_state(USB_STATE_CONFIGURED) ?
It is set in set_config in composite driver.
I think that it is sufficient. 
>
>> +		} else {
>> +			cdns3_gadget_unconfig(priv_dev);
>> +			usb_gadget_set_state(&priv_dev->gadget,
>> +					     USB_STATE_ADDRESS);
>> +		}
>> +		break;
>> +	case USB_STATE_CONFIGURED:
>> +		result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
>> +
>> +		if (!config && !result) {
>> +			cdns3_gadget_unconfig(priv_dev);
>> +			usb_gadget_set_state(&priv_dev->gadget,
>> +					     USB_STATE_ADDRESS);
>> +		}
>> +		break;
>> +	default:
>> +		result = -EINVAL;
>> +	}
>> +
>> +	return result;
>> +}
>> +
>> +/**
>> + * cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns 0 if success, error code on error
>> + */
>> +static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev,
>> +				     struct usb_ctrlrequest *ctrl_req)
>> +{
>> +	enum usb_device_state device_state = priv_dev->gadget.state;
>> +	u32 reg;
>> +	u32 addr;
>> +
>> +	addr = le16_to_cpu(ctrl_req->wValue);
>> +
>> +	if (addr > DEVICE_ADDRESS_MAX) {
>
>If DEVICE_ADDRESS_MAX comes from USB spec it must be in ch9.h.
>Maybe add something like
>
>#define	USB_DEVICE_MAX_ADDRESS	127
>
Yes, it should, but I didn't see such macro definition in ch9.h.
I will add change this to USB_DEVICE_MAX_ADDRESS. 

>> +		dev_err(&priv_dev->dev,
>> +			"Device address (%d) cannot be greater than %d\n",
>> +			addr, DEVICE_ADDRESS_MAX);
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (device_state == USB_STATE_CONFIGURED) {
>> +		dev_err(&priv_dev->dev, "USB device already configured\n");
>
>Message is misleading. How about "can't set_address from configured state"
>
Sounds better. 
>> +		return -EINVAL;
>> +	}
>> +
>> +	reg = readl(&priv_dev->regs->usb_cmd);
>> +
>> +	writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR,
>> +	       &priv_dev->regs->usb_cmd);
>> +
>> +	usb_gadget_set_state(&priv_dev->gadget,
>> +			     (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT));
>> +
>> +	cdns3_prepare_setup_packet(priv_dev);
>
>why call this here? This should be done after the current ep0 request is complete.

It only arm DMA for next SETUP packet. 
It's allow to eliminate one extra DESCMISS interrupt. This interrupt is 
generated when packet is in on-chip buffer and DMA is not started. 

Additionally, all OUT endpoints have common shared FIFO buffer. It's the reason why data from 
This on-chip buffers should be copied to system memory ASAP. Each delay before 
getting  packet  from this FIFO has impact on performance.

>> +
>> +	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>> +
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns 0 if success, error code on error
>> + */
>> +static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev,
>> +				    struct usb_ctrlrequest *ctrl)
>> +{
>> +	__le16 *response_pkt;
>> +	u16 usb_status = 0;
>> +	u32 recip;
>> +	u32 reg;
>> +
>> +	recip = ctrl->bRequestType & USB_RECIP_MASK;
>> +
>> +	switch (recip) {
>> +	case USB_RECIP_DEVICE:
>> +		/* self powered */
>> +		usb_status |= priv_dev->gadget.is_selfpowered;
>
>if (prv_devgadget.is_selfpowered)
>	usb_stats |= BIT(USB_DEVICE_SELF_POWERED);
>
Ok,
 I don't understand why, but I see such solution in MTU3 driver

In musb it is used in such way: 
result[0] = musb->g.is_selfpowered << USB_DEVICE_SELF_POWERED;

>> +
>> +		if (priv_dev->gadget.speed != USB_SPEED_SUPER)
>
>You should check controller speed directly instead.
>
>> +			break;
>> +
>> +		reg = readl(&priv_dev->regs->usb_sts);
>> +
>> +		if (priv_dev->u1_allowed)
>> +			usb_status |= BIT(USB_DEV_STAT_U1_ENABLED);
>> +
>> +		if (priv_dev->u2_allowed)
>> +			usb_status |= BIT(USB_DEV_STAT_U2_ENABLED);
>> +
>> +		if (priv_dev->wake_up_flag)
>> +			usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP);
>
>Remote wakeup is not SS specific. So needs to go before the SS check.
Yes, sure.
>
>> +		break;
>> +	case USB_RECIP_INTERFACE:
>> +		return cdns3_ep0_delegate_req(priv_dev, ctrl);
>> +	case USB_RECIP_ENDPOINT:
>> +		/* check if endpoint is stalled */
>> +		cdns3_select_ep(priv_dev, ctrl->wIndex);
>> +		if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)))
>> +			usb_status =  BIT(USB_ENDPOINT_HALT);
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +
>> +	response_pkt = (__le16 *)priv_dev->setup;
>> +	*response_pkt = cpu_to_le16(usb_status);
>> +
>> +	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
>> +			       sizeof(*response_pkt), 1);
>> +	return 0;
>> +}
>> +
>> +static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev,
>> +					   struct usb_ctrlrequest *ctrl,
>> +					   int set)
>> +{
>> +	enum usb_device_state state;
>> +	enum usb_device_speed speed;
>> +	int ret = 0;
>> +	u32 wValue;
>> +	u32 wIndex;
>> +	u16 tmode;
>> +
>> +	wValue = le16_to_cpu(ctrl->wValue);
>> +	wIndex = le16_to_cpu(ctrl->wIndex);
>> +	state = priv_dev->gadget.state;
>> +	speed = priv_dev->gadget.speed;
>> +
>> +	switch (ctrl->wValue) {
>> +	case USB_DEVICE_REMOTE_WAKEUP:
>> +		priv_dev->wake_up_flag = !!set;
>> +		break;
>> +	case USB_DEVICE_U1_ENABLE:
>> +		if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
>> +			return -EINVAL;
>> +
>> +		priv_dev->u1_allowed = !!set;
>> +		break;
>> +	case USB_DEVICE_U2_ENABLE:
>> +		if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
>> +			return -EINVAL;
>> +
>> +		priv_dev->u2_allowed = !!set;
>> +		break;
>> +	case USB_DEVICE_LTM_ENABLE:
>> +		ret = -EINVAL;
>> +		break;
>> +	case USB_DEVICE_TEST_MODE:
>> +		if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH)
>> +			return -EINVAL;
>> +
>> +		tmode = le16_to_cpu(ctrl->wIndex);
>> +
>> +		if (!set || (tmode & 0xff) != 0)
>> +			return -EINVAL;
>> +
>> +		switch (tmode >> 8) {
>> +		case TEST_J:
>> +		case TEST_K:
>> +		case TEST_SE0_NAK:
>> +		case TEST_PACKET:
>> +			cdns3_set_register_bit(&priv_dev->regs->usb_cmd,
>> +					       USB_CMD_STMODE |
>> +					       USB_STS_TMODE_SEL(tmode - 1));
>> +			break;
>> +		default:
>> +			ret = -EINVAL;
>> +		}
>> +		break;
>> +	default:
>> +		ret = -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev,
>> +					 struct usb_ctrlrequest *ctrl,
>> +					 int set)
>> +{
>> +	u32 wValue;
>> +	int ret = 0;
>> +
>> +	wValue = le16_to_cpu(ctrl->wValue);
>> +
>> +	switch (wValue) {
>> +	case USB_INTRF_FUNC_SUSPEND:
>> +		break;
>> +	default:
>> +		ret = -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev,
>> +					     struct usb_ctrlrequest *ctrl,
>> +					     int set)
>> +{
>> +	struct cdns3_endpoint *priv_ep;
>> +	int ret = 0;
>> +	u8 index;
>> +
>> +	if (!(ctrl->wIndex &  ~USB_DIR_IN))
>> +		return 0;
>
>Why is this check?

Whether  endpoint in wIndex is different then 0. I use this 
value in next line and it must be greater than 0.  
>
>> +
>> +	index = cdns3_ep_addr_to_index(ctrl->wIndex);
>> +	priv_ep = priv_dev->eps[index];
>> +
>> +	cdns3_select_ep(priv_dev, ctrl->wIndex);
>> +
>> +	if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT)
>> +		return -EINVAL;
>
>This check should be done first before you try to decode wIndex.
I understand that you mean that it doesn't make sense doing anything when endpoint is halted. 
>
>> +
>> +	if (set) {
>> +		writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
>> +		priv_ep->flags |= EP_STALL;
>> +	} else {
>> +		struct usb_request *request;
>> +
>> +		if (priv_dev->eps[index]->flags & EP_WEDGE) {
>> +			cdns3_select_ep(priv_dev, 0x00);
>> +			return 0;
>> +		}
>> +
>> +		writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
>> +
>> +		/* wait for EPRST cleared */
>> +		ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
>> +				      EP_CMD_EPRST, 0, 100);
>> +		if (ret)
>> +			return -EINVAL;
>> +
>> +		priv_ep->flags &= ~EP_STALL;
>> +
>> +		request = cdns3_next_request(&priv_ep->request_list);
>> +		if (request)
>> +			cdns3_ep_run_transfer(priv_ep, request);
>> +	}
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_req_ep0_handle_feature -
>> + * Handling of GET/SET_FEATURE standard USB request
>> + *
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + * @set: must be set to 1 for SET_FEATURE request
>> + *
>> + * Returns 0 if success, error code on error
>> + */
>> +static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev,
>> +					struct usb_ctrlrequest *ctrl,
>> +					int set)
>> +{
>> +	int ret = 0;
>> +	u32 recip;
>> +
>> +	recip = ctrl->bRequestType & USB_RECIP_MASK;
>> +
>> +	switch (recip) {
>> +	case USB_RECIP_DEVICE:
>> +		ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set);
>> +		break;
>> +	case USB_RECIP_INTERFACE:
>> +		ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set);
>> +		break;
>> +	case USB_RECIP_ENDPOINT:
>> +		ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set);
>> +		break;
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +
>> +	if (!ret)
>> +		writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>> +
>> +	return ret;
>> +}
>> +
>> +/**
>> + * cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns 0 if success, error code on error
>> + */
>> +static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev,
>> +				 struct usb_ctrlrequest *ctrl_req)
>> +{
>> +	if (priv_dev->gadget.state < USB_STATE_ADDRESS)
>> +		return -EINVAL;
>> +
>> +	if (ctrl_req->wLength != 6) {
>> +		dev_err(&priv_dev->dev, "Set SEL should be 6 bytes, got %d\n",
>> +			ctrl_req->wLength);
>> +		return -EINVAL;
>> +	}
>> +
>> +	priv_dev->ep0_data_dir = 0;
>> +	cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_req_ep0_set_isoch_delay -
>> + * Handling of GET_ISOCH_DELAY standard USB request
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns 0 if success, error code on error
>> + */
>> +static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev,
>> +					 struct usb_ctrlrequest *ctrl_req)
>> +{
>> +	if (ctrl_req->wIndex || ctrl_req->wLength)
>> +		return -EINVAL;
>> +
>> +	priv_dev->isoch_delay = ctrl_req->wValue;
>> +	writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * cdns3_ep0_standard_request - Handling standard USB requests
>> + * @priv_dev: extended gadget object
>> + * @ctrl_req: pointer to received setup packet
>> + *
>> + * Returns 0 if success, error code on error
>> + */
>> +static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev,
>> +				      struct usb_ctrlrequest *ctrl_req)
>> +{
>> +	int ret;
>> +
>> +	switch (ctrl_req->bRequest) {
>> +	case USB_REQ_SET_ADDRESS:
>> +		ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req);
>> +		break;
>> +	case USB_REQ_SET_CONFIGURATION:
>> +		ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req);
>> +		break;
>> +	case USB_REQ_GET_STATUS:
>> +		ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req);
>> +		break;
>> +	case USB_REQ_CLEAR_FEATURE:
>> +		ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0);
>> +		break;
>> +	case USB_REQ_SET_FEATURE:
>> +		ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1);
>> +		break;
>> +	case USB_REQ_SET_SEL:
>> +		ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req);
>> +		break;
>> +	case USB_REQ_SET_ISOCH_DELAY:
>> +		ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req);
>> +		break;
>> +	default:
>> +		ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
>> +		break;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>>  static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
>>  {
>> -	//TODO: Implements this function
>> +	struct usb_request *request = priv_dev->pending_status_request;
>> +
>> +	if (priv_dev->status_completion_no_call && request &&
>> +	    request->complete) {
>> +		request->complete(priv_dev->gadget.ep0, request);
>> +		priv_dev->status_completion_no_call = 0;
>> +	}
>> +}
>> +
>> +void cdns3_pending_setup_status_handler(struct work_struct *work)
>> +{
>> +	struct cdns3_device *priv_dev = container_of(work, struct cdns3_device,
>> +			pending_status_wq);
>> +	unsigned long flags;
>> +
>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>> +	__pending_setup_status_handler(priv_dev);
>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>>  }
>>
>>  /**
>> @@ -101,12 +546,50 @@ static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
>>   */
>>  static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
>>  {
>> -	//TODO: Implements this function.
>> +	struct usb_ctrlrequest *ctrl = priv_dev->setup;
>> +	int result;
>> +
>> +	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
>> +		result = cdns3_ep0_standard_request(priv_dev, ctrl);
>> +	else
>> +		result = cdns3_ep0_delegate_req(priv_dev, ctrl);
>> +
>> +	if (result != 0 && result != USB_GADGET_DELAYED_STATUS) {
>> +		dev_dbg(&priv_dev->dev, "STALL for ep0\n");
>> +		/* set_stall on ep0 */
>> +		cdns3_select_ep(priv_dev, 0x00);
>> +		writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
>> +		writel(EP_CMD_ERDY | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>> +	}
>>  }
>>
>>  static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
>>  {
>> -	//TODO: Implements this function
>> +	if (priv_dev->ep0_request) {
>> +		usb_gadget_unmap_request_by_dev(priv_dev->sysdev,
>> +						priv_dev->ep0_request,
>> +						priv_dev->ep0_data_dir);
>> +
>> +		priv_dev->ep0_request->actual =
>> +			TRB_LEN(le32_to_cpu(priv_dev->trb_ep0->length));
>> +
>> +		dev_dbg(&priv_dev->dev, "Ep0 completion length %d\n",
>> +			priv_dev->ep0_request->actual);
>> +		list_del_init(&priv_dev->ep0_request->list);
>> +	}
>> +
>> +	if (priv_dev->ep0_request &&
>> +	    priv_dev->ep0_request->complete) {
>> +		spin_unlock(&priv_dev->lock);
>> +		priv_dev->ep0_request->complete(priv_dev->gadget.ep0,
>> +						priv_dev->ep0_request);
>> +
>> +		priv_dev->ep0_request = NULL;
>> +		spin_lock(&priv_dev->lock);
>> +	}
>> +
>> +	cdns3_prepare_setup_packet(priv_dev);
>> +	writel(EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd);
>>  }
>>
>>  /**
>> diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c
>> index 309202474e57..0202ff5f6c90 100644
>> --- a/drivers/usb/cdns3/gadget.c
>> +++ b/drivers/usb/cdns3/gadget.c
>> @@ -70,6 +70,30 @@ static u8 cdns3_ep_reg_pos_to_index(int i)
>>  	return ((i / 16) + (((i % 16) - 2) * 2));
>>  }
>>
>> +/**
>> + * cdns3_ep_addr_to_index - Macro converts endpoint address to
>> + * index of endpoint object in cdns3_device.eps[] container
>> + * @ep_addr: endpoint address for which endpoint object is required
>> + *
>> + * Remember that endpoint container doesn't contain default endpoint
>> + */
>> +u8 cdns3_ep_addr_to_index(u8 ep_addr)
>> +{
>> +	return (((ep_addr & 0x7F) - 1) + ((ep_addr & USB_DIR_IN) ? 1 : 0));
>> +}
>> +
>> +/**
>> + * cdns3_ep_addr_to_bit_pos - Macro converts endpoint address to
>> + * bit position in ep_ists register
>> + * @ep_addr: endpoint address for which bit position is required
>> + *
>> + * Remember that endpoint container doesn't contain default endpoint
>> + */
>> +static u32 cdns3_ep_addr_to_bit_pos(u8 ep_addr)
>> +{
>> +	return (1 << (ep_addr & 0x7F)) << ((ep_addr & 0x80) ? 16 : 0);
>> +}
>> +
>>  /**
>>   * cdns3_next_request - returns next request from list
>>   * @list: list containing requests
>> @@ -464,6 +488,99 @@ static irqreturn_t cdns3_irq_handler_thread(struct cdns3 *cdns)
>>  	return ret;
>>  }
>>
>> +/**
>> + * cdns3_ep_onchip_buffer_alloc - Try to allocate onchip buf for EP
>> + *
>> + * The real allocation will occur during write to EP_CFG register,
>> + * this function is used to check if the 'size' allocation is allowed.
>> + *
>> + * @priv_dev: extended gadget object
>> + * @size: the size (KB) for EP would like to allocate
>> + * @is_in: the direction for EP
>> + *
>> + * Return 0 if the later allocation is allowed or negative value on failure
>> + */
>> +static int cdns3_ep_onchip_buffer_alloc(struct cdns3_device *priv_dev,
>> +					int size, int is_in)
>> +{
>> +	if (is_in) {
>> +		priv_dev->onchip_mem_allocated_size += size;
>> +	} else if (!priv_dev->out_mem_is_allocated) {
>> +		 /* ALL OUT EPs are shared the same chunk onchip memory */
>> +		priv_dev->onchip_mem_allocated_size += size;
>> +		priv_dev->out_mem_is_allocated = 1;
>> +	}
>> +
>> +	if (priv_dev->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) {
>> +		priv_dev->onchip_mem_allocated_size -= size;
>> +		return -EPERM;
>> +	} else {
>> +		return 0;
>> +	}
>> +}
>> +
>> +/**
>> + * cdns3_ep_config Configure hardware endpoint
>> + * @priv_ep: extended endpoint object
>> + */
>> +void cdns3_ep_config(struct cdns3_endpoint *priv_ep)
>> +{
>> +	bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC);
>> +	struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
>> +	u32 bEndpointAddress = priv_ep->num | priv_ep->dir;
>> +	u32 interrupt_mask = EP_STS_EN_TRBERREN;
>> +	u32 max_packet_size = 0;
>> +	u32 ep_cfg = 0;
>> +	int ret;
>> +
>> +	if (priv_ep->type == USB_ENDPOINT_XFER_INT) {
>> +		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT);
>> +	} else if (priv_ep->type == USB_ENDPOINT_XFER_BULK) {
>> +		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK);
>> +	} else {
>> +		ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC);
>> +		interrupt_mask = 0xFFFFFFFF;
>> +	}
>> +
>> +	switch (priv_dev->gadget.speed) {
>> +	case USB_SPEED_FULL:
>> +		max_packet_size = is_iso_ep ? 1023 : 64;
>> +		break;
>> +	case USB_SPEED_HIGH:
>> +		max_packet_size = is_iso_ep ? 1024 : 512;
>> +		break;
>> +	case USB_SPEED_SUPER:
>> +		max_packet_size = 1024;
>> +		break;
>> +	default:
>> +		//all other speed are not supported
>> +		return;
>> +	}
>> +
>> +	ret = cdns3_ep_onchip_buffer_alloc(priv_dev, CDNS3_EP_BUF_SIZE,
>> +					   priv_ep->dir);
>
>where do you free the buffer_alloc?
I don't free this buffer. 
This function was added by Peter Chan, and it refer to on-chip buffer. 
Peter in his platform has limited number of on-chip memory. If I remember 
correctly It has only 16KB. It's kind of software protection against exceeding this buffer.
It's hard coded now in driver. I think that this value can be read from  register, 
I have to consult this with hardware team.

I think the cdns3_ep_onchip_buffer_reserve is a better name.
It is not associated with allocation of memory. 
>
>> +	if (ret) {
>> +		dev_err(&priv_dev->dev, "onchip mem is full, ep is invalid\n");
>> +		return;
>> +	}
>> +
>> +	ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) |
>> +		  EP_CFG_BUFFERING(CDNS3_EP_BUF_SIZE - 1) |
>> +		  EP_CFG_MAXBURST(priv_ep->endpoint.maxburst);
>> +
>> +	cdns3_select_ep(priv_dev, bEndpointAddress);
>> +
>> +	writel(ep_cfg, &priv_dev->regs->ep_cfg);
>> +	writel(interrupt_mask, &priv_dev->regs->ep_sts_en);
>> +
>> +	dev_dbg(&priv_dev->dev, "Configure %s: with val %08x\n",
>> +		priv_ep->name, ep_cfg);
>> +
>> +	/* enable interrupt for selected endpoint */
>> +	cdns3_set_register_bit(&priv_dev->regs->ep_ien,
>> +			       cdns3_ep_addr_to_bit_pos(bEndpointAddress));
>> +}
>> +
>>  /* Find correct direction for HW endpoint according to description */
>>  static int cdns3_ep_dir_is_correct(struct usb_endpoint_descriptor *desc,
>>  				   struct cdns3_endpoint *priv_ep)
>> @@ -1104,6 +1221,8 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
>>  	priv_dev->is_connected = 0;
>>
>>  	spin_lock_init(&priv_dev->lock);
>> +	INIT_WORK(&priv_dev->pending_status_wq,
>> +		  cdns3_pending_setup_status_handler);
>>
>>  	priv_dev->in_standby_mode = 1;
>>
>> diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h
>> index 8c2f363f9340..db8c6cb9f2a5 100644
>> --- a/drivers/usb/cdns3/gadget.h
>> +++ b/drivers/usb/cdns3/gadget.h
>> @@ -1070,14 +1070,18 @@ struct cdns3_device {
>>
>>  int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec);
>>  void cdns3_set_register_bit(void __iomem *ptr, u32 mask);
>> +void cdns3_pending_setup_status_handler(struct work_struct *work);
>>  int cdns3_init_ep0(struct cdns3_device *priv_dev);
>>  void cdns3_ep0_config(struct cdns3_device *priv_dev);
>> +void cdns3_ep_config(struct cdns3_endpoint *priv_ep);
>>  void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir);
>>  void cdns3_select_ep(struct cdns3_device *priv_dev, u32 ep);
>>  void cdns3_enable_l1(struct cdns3_device *priv_dev, int enable);
>>  struct usb_request *cdns3_next_request(struct list_head *list);
>> +void cdns3_gadget_unconfig(struct cdns3_device *priv_dev);
>>  int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
>>  			  struct usb_request *request);
>> +u8 cdns3_ep_addr_to_index(u8 ep_addr);
>>  int cdns3_gadget_ep_set_wedge(struct usb_ep *ep);
>>  int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value);
>>  struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep,
>>
Cheers	
Pawel

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

* RE: [RFC PATCH v2 02/15] usb:cdns3: Device side header file.
  2018-11-30  6:48   ` PETER CHEN
@ 2018-12-02 19:27     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-02 19:27 UTC (permalink / raw)
  To: PETER CHEN, devicetree
  Cc: gregkh, linux-usb, rogerq, linux-kernel, Alan Douglas,
	jbergsagel, nsekhar, nm, Suresh Punnoose, Pawel Jez, Rahul Kumar

Hi Peter
>> +
>> +/*
>> + * USBSS-DEV register interface.
>> + * This corresponds to the USBSS Device Controller Interface  */
>> +/**
>> + * struct xhci_cap_regs - xHCI Host Controller Capability Registers.
>
>struct cdns3_usb_regs - device controller registers

thanks,  
I had this from beginning. I don't know why.  
I 
>
>> +struct cdns3_device;
>> +
>> +struct cdns3_endpoint {
>> +	struct usb_ep		endpoint;
>> +	struct list_head	request_list;
>> +	struct list_head	ep_match_pending_list;
>> +
>> +	struct cdns3_trb	*trb_pool;
>> +	dma_addr_t		trb_pool_dma;
>> +
>> +	struct cdns3_device	*cdns3_dev;
>> +	char			name[20];
>> +
>> +#define EP_ENABLED		BIT(0)
>> +#define EP_STALL		BIT(1)
>> +#define EP_WEDGE		BIT(2)
>> +#define EP_TRANSFER_STARTED	BIT(3)
>> +#define EP_UPDATE_EP_TRBADDR	BIT(4)
>> +#define EP_PENDING_REQUEST	BIT(5)
>> +#define EP_USED			BIT(5)
>> +	u32			flags;
>> +
>> +	void			*aligned_buff;
>> +	dma_addr_t		aligned_dma_addr;
>> +	u8			dir;
>> +	u8			num;
>> +	u8			type;
>> +
>> +	int			free_trbs;
>> +	u8			pcs;
>> +	u8			ccs;
>> +	int			enqueue;
>> +	int			dequeue;
>> +};
>> +
>
>Would you please add kernel doc for above structure?

Done.
>
>> +struct cdns3_request {
>> +	struct usb_request request;
>> +	struct cdns3_endpoint *priv_ep;
>> +	struct cdns3_trb *trb;
>> +	int start_trb;
>> +	int end_trb;
>> +	int on_ring:1;
>> +};
>> +
>> +#define to_cdns3_request(r) (container_of(r, struct cdns3_request,
>> +request))
>> +
>> +struct cdns3_device {
>> +	struct device			dev;
>> +	struct cdns3_usb_regs		__iomem *regs;
>> +
>> +	struct usb_gadget		gadget;
>> +	struct usb_gadget_driver	*gadget_driver;
>> +
>> +	struct usb_ctrlrequest		*setup;
>> +	dma_addr_t			setup_dma;
>> +	dma_addr_t			trb_ep0_dma;
>> +	struct cdns3_trb		*trb_ep0;
>> +	void				*zlp_buf;
>> +
>> +	struct cdns3_endpoint		*eps[USB_SS_ENDPOINTS_MAX_COUNT];
>> +	int				ep_nums;
>> +	/*
>> +	 * field used for improving performance. It holds the last
>> +	 * selected endpoint number
>> +	 */
>> +	u32				selected_ep;
>> +	struct usb_request		*ep0_request;
>> +	int				ep0_data_dir;
>> +	int				hw_configured_flag;
>> +	int				wake_up_flag;
>> +	u16				isoch_delay;
>> +	/* generic spin-lock for drivers */
>> +	spinlock_t			lock;
>> +
>> +	unsigned			is_connected:1;
>> +	unsigned			in_standby_mode:1;
>> +	unsigned			status_completion_no_call:1;
>> +	unsigned			u1_allowed:1;
>> +	unsigned			u2_allowed:1;
>> +
>> +	u32				usb_ien;
>> +	u32				ep_ien;
>> +	int				setup_pending;
>> +	struct device			*sysdev;
>> +	/* The device mode is enabled */
>> +	int				start_gadget;
>> +	struct list_head		ep_match_list;
>> +	/* KB */
>> +	int				onchip_mem_allocated_size;
>> +	/* Memory is allocated for OUT */
>> +	int				out_mem_is_allocated:1;
>> +	struct work_struct		pending_status_wq;
>> +	struct usb_request		*pending_status_request;
>> +};
>> +

Cheers
Pawel

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-30  7:32   ` Peter Chen
@ 2018-12-02 20:34     ` Pawel Laszczak
  2018-12-04  7:11       ` Peter Chen
  0 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-02 20:34 UTC (permalink / raw)
  To: Peter Chen
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	Alan Douglas, jbergsagel, nsekhar, nm, Suresh Punnoose,
	peter.chen, Pawel Jez, Rahul Kumar

>>
>> Patch adds core.c and core.h file that implements initialization
>> of platform driver and adds function responsible for selecting,
>> switching and running appropriate Device/Host mode.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/Makefile |   2 +
>>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
>>  drivers/usb/cdns3/core.h   | 100 +++++++++
>>  3 files changed, 515 insertions(+)
>>  create mode 100644 drivers/usb/cdns3/core.c
>>  create mode 100644 drivers/usb/cdns3/core.h
>>
>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> index dcdd62003c6a..02d25b23c5d3 100644
>> --- a/drivers/usb/cdns3/Makefile
>> +++ b/drivers/usb/cdns3/Makefile
>> @@ -1,3 +1,5 @@
>> +obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
>>
>> +cdns3-y                                        := core.o
>>  cdns3-pci-y                            := cdns3-pci-wrap.o
>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> new file mode 100644
>> index 000000000000..f9055d4da67f
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/core.c
>> @@ -0,0 +1,413 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver.
>> + *
>> + * Copyright (C) 2018 Cadence.
>> + *
>
>Please add NXP copyright too.

Ok, I don't know why I omitted this. 
I know that you are the main author of this file 
Sorry for that.

One additional question. What year I should add in Copyright for NXP?. 
The original year 2017 or I should modified all to 2018. 

>> + * Author: Peter Chen <peter.chen@nxp.com>
>> + *         Pawel Laszczak <pawell@cadence.com>
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "gadget.h"
>> +#include "core.h"
>> +
>> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>> +{
>> +       WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
>> +       return cdns->roles[cdns->role];
>> +}
>> +
>
>Can we delete "current", and use cdns3_get_role_driver directly?

Yes, sure. Role is always current.
>> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
>> +{
>> +       int ret;
>> +
>> +       if (role >= CDNS3_ROLE_END)
>> +               return 0;
>> +
>> +       if (!cdns->roles[role])
>> +               return -ENXIO;
>> +
>> +       mutex_lock(&cdns->mutex);
>> +       cdns->role = role;
>> +       ret = cdns->roles[role]->start(cdns);
>> +       mutex_unlock(&cdns->mutex);
>> +       return ret;
>> +}
>> +
>> +static inline void cdns3_role_stop(struct cdns3 *cdns)
>> +{
>> +       enum cdns3_roles role = cdns->role;
>> +
>> +       if (role == CDNS3_ROLE_END)
>> +               return;
>> +
>> +       mutex_lock(&cdns->mutex);
>> +       cdns->roles[role]->stop(cdns);
>> +       cdns->role = CDNS3_ROLE_END;
>> +       mutex_unlock(&cdns->mutex);
>> +}
>> +
>> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>> +{
>> +       if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> +               //TODO: implements selecting device/host mode
>> +               return CDNS3_ROLE_HOST;
>> +       }
>> +       return cdns->roles[CDNS3_ROLE_HOST]
>> +               ? CDNS3_ROLE_HOST
>> +               : CDNS3_ROLE_GADGET;
>> +}
>> +
>> +/**
>> + * cdns3_core_init_role - initialize role of operation
>> + * @cdns: Pointer to cdns3 structure
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static int cdns3_core_init_role(struct cdns3 *cdns)
>> +{
>> +       struct device *dev = cdns->dev;
>> +       enum usb_dr_mode dr_mode;
>> +
>> +       dr_mode = usb_get_dr_mode(dev);
>> +       cdns->role = CDNS3_ROLE_END;
>> +
>> +       /*
>> +        * If driver can't read mode by means of usb_get_dr_mdoe function then
>> +        * chooses mode according with Kernel configuration. This setting
>> +        * can be restricted later depending on strap pin configuration.
>> +        */
>> +       if (dr_mode == USB_DR_MODE_UNKNOWN) {
>> +               if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
>> +                   IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> +                       dr_mode = USB_DR_MODE_OTG;
>> +               else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
>> +                       dr_mode = USB_DR_MODE_HOST;
>> +               else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> +                       dr_mode = USB_DR_MODE_PERIPHERAL;
>> +       }
>> +
>> +       if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>> +               //TODO: implements host initialization
>> +       }
>> +
>> +       if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>> +               //TODO: implements device initialization
>> +       }
>> +
>> +       if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>> +               dev_err(dev, "no supported roles\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       cdns->dr_mode = dr_mode;
>> +       return 0;
>> +}
>> +
>> +/**
>> + * cdns3_irq - interrupt handler for cdns3 core device
>> + *
>> + * @irq: irq number for cdns3 core device
>> + * @data: structure of cdns3
>> + *
>> + * Returns IRQ_HANDLED or IRQ_NONE
>> + */
>> +static irqreturn_t cdns3_irq(int irq, void *data)
>> +{
>> +       struct cdns3 *cdns = data;
>> +       irqreturn_t ret = IRQ_NONE;
>> +
>> +       /* Handle device/host interrupt */
>> +       if (cdns->role != CDNS3_ROLE_END)
>> +               ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>> +
>> +       return ret;
>> +}
>> +
>> +static void cdns3_remove_roles(struct cdns3 *cdns)
>> +{
>> +       //TODO: implements this function
>> +}
>> +
>> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>> +{
>> +       enum cdns3_roles current_role;
>> +       int ret = 0;
>> +
>> +       current_role = cdns->role;
>> +
>> +       if (role == CDNS3_ROLE_END)
>> +               return 0;
>> +
>> +       dev_dbg(cdns->dev, "Switching role");
>> +
>> +       ret = cdns3_role_start(cdns, role);
>> +       if (ret) {
>> +               /* Back to current role */
>> +               dev_err(cdns->dev, "set %d has failed, back to %d\n",
>> +                       role, current_role);
>> +               ret = cdns3_role_start(cdns, current_role);
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +/**
>> + * cdns3_role_switch - work queue handler for role switch
>> + *
>> + * @work: work queue item structure
>> + *
>> + * Handles below events:
>> + * - Role switch for dual-role devices
>> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
>> + */
>> +static void cdns3_role_switch(struct work_struct *work)
>> +{
>> +       enum cdns3_roles role = CDNS3_ROLE_END;
>> +       struct cdns3 *cdns;
>> +       bool device, host;
>> +
>> +       cdns = container_of(work, struct cdns3, role_switch_wq);
>> +
>> +       //TODO: implements this functions.
>> +       //host = cdns3_is_host(cdns);
>> +       //device = cdns3_is_device(cdns);
>
>You may improve use C comment.
Yes, I know. I've already done that. 
>
>> +       host = 1;
>> +       device = 0;
>> +
>> +       if (host)
>> +               role = CDNS3_ROLE_HOST;
>> +       else if (device)
>> +               role = CDNS3_ROLE_GADGET;
>> +
>> +       if (cdns->desired_dr_mode == cdns->current_dr_mode &&
>> +           cdns->role == role)
>> +               return;
>> +
>> +       pm_runtime_get_sync(cdns->dev);
>> +       cdns3_role_stop(cdns);
>> +
>> +       if (host) {
>> +               if (cdns->roles[CDNS3_ROLE_HOST])
>> +                       cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>> +               pm_runtime_put_sync(cdns->dev);
>> +               return;
>> +       }
>> +
>> +       if (device)
>> +               cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
>> +       else
>> +               cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
>> +
>> +       pm_runtime_put_sync(cdns->dev);
>> +}
>> +
>> +/**
>> + * cdns3_probe - probe for cdns3 core device
>> + * @pdev: Pointer to cdns3 core platform device
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static int cdns3_probe(struct platform_device *pdev)
>> +{
>> +       struct device *dev = &pdev->dev;
>> +       struct resource *res;
>> +       struct cdns3 *cdns;
>> +       void __iomem *regs;
>> +       int ret;
>> +
>> +       cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
>> +       if (!cdns)
>> +               return -ENOMEM;
>> +
>> +       cdns->dev = dev;
>> +
>> +       platform_set_drvdata(pdev, cdns);
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
>> +       if (!res) {
>> +               dev_err(dev, "missing IRQ\n");
>> +               return -ENODEV;
>> +       }
>> +       cdns->irq = res->start;
>> +
>> +       /*
>> +        * Request memory region
>> +        * region-0: xHCI
>> +        * region-1: Peripheral
>> +        * region-2: OTG registers
>> +        */
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       regs = devm_ioremap_resource(dev, res);
>> +
>> +       if (IS_ERR(regs))
>> +               return PTR_ERR(regs);
>> +       cdns->xhci_regs = regs;
>> +       cdns->xhci_res = res;
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> +       regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(regs))
>> +               return PTR_ERR(regs);
>> +       cdns->dev_regs  = regs;
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>> +       regs = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(regs))
>> +               return PTR_ERR(regs);
>> +       cdns->otg_regs = regs;
>> +
>> +       mutex_init(&cdns->mutex);
>> +
>> +       cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>> +       if (IS_ERR(cdns->phy)) {
>> +               dev_info(dev, "no generic phy found\n");
>> +               cdns->phy = NULL;
>> +               /*
>> +                * fall through here!
>> +                * if no generic phy found, phy init
>> +                * should be done under boot!
>> +                */
>
>If the phy driver is defer-probed, it will be here, it is not an error.
>I think you could have a generic phy driver or usb generic phy driver
>(drivers/usb/phy/phy-generic.c) even you don't need any operations for
>PHY. It will be easy for other platforms.

Yes, Roger ask me to modify this fragment. In next version it will look like:
	cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
	if (IS_ERR(cdns->phy)) {
		ret = PTR_ERR(cdns->phy);
		if (ret == -ENOSYS || ret == -ENODEV) {
			cdns->phy = NULL;
		} else if (ret == -EPROBE_DEFER) {
			return ret;
		} else {
			dev_err(dev, "no phy found\n");
			goto err0;
		}
	}

	phy_init(cdns->phy);

We are going to use phy driver. I don't know if it correct. 
I don't have experience in this filed.  
We need phy initialization but I don't have testing platform now.  
In most usb drivers I see that there are used usb phy driverd instead phy dirverd.

>> +       } else {
>> +               phy_init(cdns->phy);
>> +       }
>> +
>> +       ret = cdns3_core_init_role(cdns);
>> +       if (ret)
>> +               goto err1;
>> +
>> +       INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
>> +       if (ret)
>> +               goto err2;
>> +
>> +       if (ret)
>> +               goto err2;
>> +
>> +       cdns->role = cdns3_get_role(cdns);
>> +
>> +       ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
>> +                              dev_name(dev), cdns);
>> +
>> +       if (ret)
>> +               goto err2;
>> +
>> +       ret = cdns3_role_start(cdns, cdns->role);
>> +       if (ret) {
>> +               dev_err(dev, "can't start %s role\n",
>> +                       cdns3_get_current_role_driver(cdns)->name);
>> +               goto err2;
>> +       }
>> +
>> +       device_set_wakeup_capable(dev, true);
>> +       pm_runtime_set_active(dev);
>> +       pm_runtime_enable(dev);
>> +
>> +       /*
>> +        * The controller needs less time between bus and controller suspend,
>> +        * and we also needs a small delay to avoid frequently entering low
>> +        * power mode.
>> +        */
>> +       pm_runtime_set_autosuspend_delay(dev, 20);
>> +       pm_runtime_mark_last_busy(dev);
>> +       pm_runtime_use_autosuspend(dev);
>> +       dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
>> +
>> +       return 0;
>> +
>> +err2:
>> +       cdns3_remove_roles(cdns);
>> +err1:
>> +       return ret;
>> +}
>> +
>> +/**
>> + * cdns3_remove - unbind drd driver and clean up
>> + * @pdev: Pointer to Linux platform device
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static int cdns3_remove(struct platform_device *pdev)
>> +{
>> +       struct cdns3 *cdns = platform_get_drvdata(pdev);
>> +
>> +       pm_runtime_get_sync(&pdev->dev);
>> +       pm_runtime_disable(&pdev->dev);
>> +       pm_runtime_put_noidle(&pdev->dev);
>> +       cdns3_remove_roles(cdns);
>> +
>> +       return 0;
>> +}
>> +
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id of_cdns3_match[] = {
>> +       { .compatible = "cdns,usb3" },
>> +       { },
>> +};
>> +MODULE_DEVICE_TABLE(of, of_cdns3_match);
>> +#endif
>> +
>> +#ifdef CONFIG_PM
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int cdns3_suspend(struct device *dev)
>> +{
>> +       //TODO: implements this function
>> +       return 0;
>> +}
>> +
>> +static int cdns3_resume(struct device *dev)
>> +{
>> +       //TODO: implements this function
>> +       return 0;
>> +}
>> +#endif /* CONFIG_PM_SLEEP */
>> +static int cdns3_runtime_suspend(struct device *dev)
>> +{      //TODO: implements this function
>> +       return 0;
>> +}
>> +
>> +static int cdns3_runtime_resume(struct device *dev)
>> +{
>> +       //TODO: implements this function
>> +       return 0;
>> +}
>> +#endif /* CONFIG_PM */
>> +
>> +static const struct dev_pm_ops cdns3_pm_ops = {
>> +       SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
>> +       SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
>> +};
>> +
>> +static struct platform_driver cdns3_driver = {
>> +       .probe          = cdns3_probe,
>> +       .remove         = cdns3_remove,
>> +       .driver         = {
>> +               .name   = "cdns-usb3",
>> +               .of_match_table = of_match_ptr(of_cdns3_match),
>> +               .pm     = &cdns3_pm_ops,
>> +       },
>> +};
>> +
>> +static int __init cdns3_driver_platform_register(void)
>> +{
>> +       return platform_driver_register(&cdns3_driver);
>> +}
>> +module_init(cdns3_driver_platform_register);
>> +
>> +static void __exit cdns3_driver_platform_unregister(void)
>> +{
>> +       platform_driver_unregister(&cdns3_driver);
>> +}
>> +module_exit(cdns3_driver_platform_unregister);
>> +
>> +MODULE_ALIAS("platform:cdns3");
>> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
>> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
>> new file mode 100644
>> index 000000000000..7c8204fe4d3d
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/core.h
>> @@ -0,0 +1,100 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Cadence USBSS DRD Driver.
>> + *
>
>Header file
I don't understand. What is wrong ?
>
>> + * Copyright (C) 2017 NXP
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Authors: Peter Chen <peter.chen@nxp.com>
>> + *          Pawel Laszczak <pawell@cadence.com>
>> + */
>> +#include <linux/usb/otg.h>
>> +
>> +#ifndef __LINUX_CDNS3_CORE_H
>> +#define __LINUX_CDNS3_CORE_H
>> +
>> +struct cdns3;
>> +enum cdns3_roles {
>> +       CDNS3_ROLE_HOST = 0,
>> +       CDNS3_ROLE_GADGET,
>> +       CDNS3_ROLE_END,
>> +};
>> +
>> +/**
>> + * struct cdns3_role_driver - host/gadget role driver
>> + * @start: start this role
>> + * @stop: stop this role
>> + * @suspend: suspend callback for this role
>> + * @resume: resume callback for this role
>> + * @irq: irq handler for this role
>> + * @name: role name string (host/gadget)
>> + */
>> +struct cdns3_role_driver {
>> +       int (*start)(struct cdns3 *cdns);
>> +       void (*stop)(struct cdns3 *cdns);
>> +       int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
>> +       int (*resume)(struct cdns3 *cdns, bool hibernated);
>> +       irqreturn_t (*irq)(struct cdns3 *cdns);
>> +       const char *name;
>> +};
>> +
>> +#define CDNS3_NUM_OF_CLKS      5
>> +/**
>> + * struct cdns3 - Representation of Cadence USB3 DRD controller.
>> + * @dev: pointer to Cadence device struct
>> + * @xhci_regs: pointer to base of xhci registers
>> + * @xhci_res: the resource for xhci
>> + * @dev_regs: pointer to base of dev registers
>> + * @otg_regs: pointer to base of otg registers
>> + * @irq: irq number for controller
>> + * @roles: array of supported roles for this controller
>> + * @role: current role
>> + * @host_dev: the child host device pointer for cdns3 core
>> + * @gadget_dev: the child gadget device pointer for cdns3 core
>> + * @usb: phy for this controller
>> + * @role_switch_wq: work queue item for role switch
>> + * @in_lpm: the controller in low power mode
>> + * @wakeup_int: the wakeup interrupt
>> + * @mutex: the mutex for concurrent code at driver
>> + * @dr_mode: supported mode of operation it can be only Host, only Device
>> + *           or OTG mode that allow to switch between Device and Host mode.
>> + *           This field based on hardware configuration and cant't be changed.
>
>Based on firmware setting, kernel configuration and hardware configuration.
I removed this line but this will be better. 
Thanks.
>
>> + * @current_dr_role: current mode of operation when in dual-role mode
>> + * @desired_dr_role: desired mode of operation when in dual-role mode.
>> + *           This value can be changed during runtime.
>> + *           Available options depends on  dr_mode:
>> + *           dr_mode                 |  desired_dr_role and current_dr_role
>> + *           ----------------------------------------------------------------
>> + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
>> + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
>> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
>> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
>> + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
>> + *
>> + *           Desired_dr_role can be changed by means of debugfs.
>> + * @root: debugfs root folder pointer
>> + */
>> +struct cdns3 {
>> +       struct device                   *dev;
>> +       void __iomem                    *xhci_regs;
>> +       struct resource                 *xhci_res;
>> +       struct cdns3_usb_regs __iomem   *dev_regs;
>> +       struct cdns3_otg_regs           *otg_regs;
>> +       int irq;
>> +       struct cdns3_role_driver        *roles[CDNS3_ROLE_END];
>> +       enum cdns3_roles                role;
>> +       struct device                   *host_dev;
>> +       struct device                   *gadget_dev;
>> +       struct phy                      *phy;
>> +       struct work_struct              role_switch_wq;
>> +       int                             in_lpm:1;
>> +       int                             wakeup_int:1;
>> +       /* mutext used in workqueue*/
>> +       struct mutex                    mutex;
>> +       enum usb_dr_mode                dr_mode;
>> +       enum usb_dr_mode                current_dr_mode;
>> +       enum usb_dr_mode                desired_dr_mode;
>> +       struct dentry                   *root;
>> +};
>> +
>> +#endif /* __LINUX_CDNS3_CORE_H */
>> --
>> 2.17.1
>>
Thank 
Cheers
Pawel


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

* RE: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-01 11:11     ` Pawel Laszczak
@ 2018-12-03 10:19       ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-03 10:19 UTC (permalink / raw)
  To: Roger Quadros, devicetree
  Cc: gregkh, linux-usb, linux-kernel, Alan Douglas, jbergsagel,
	nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez, Rahul Kumar,
	Felipe Balbi

Hi
>>> +
>>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>>> +					    struct usb_endpoint_descriptor *desc,
>>> +					    struct usb_ss_ep_comp_descriptor *comp_desc)
>>> +{
>>> +	struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>>> +	struct cdns3_endpoint *priv_ep;
>>> +	unsigned long flags;
>>> +
>>> +	priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
>>> +	if (IS_ERR(priv_ep)) {
>>> +		dev_err(&priv_dev->dev, "no available ep\n");
>>> +		return NULL;
>>> +	}
>>> +
>>> +	dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
>>> +
>>> +	spin_lock_irqsave(&priv_dev->lock, flags);
>>> +	priv_ep->endpoint.desc = desc;
>>> +	priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
>>> +	priv_ep->type = usb_endpoint_type(desc);
>>> +
>>> +	list_add_tail(&priv_ep->ep_match_pending_list,
>>> +		      &priv_dev->ep_match_list);
>>> +	spin_unlock_irqrestore(&priv_dev->lock, flags);
>>> +	return &priv_ep->endpoint;
>>> +}
>>
>>Why do you need a custom match_ep?
>>doesn't usb_ep_autoconfig suffice?
>
>I need to test it but at first glance it  looks like usb_ep_autoconfig suffice.
>
>>
>>You can check if EP is claimed or not by checking the ep->claimed flag.
>>
I checked it and did not work correct. The flag claimed is set in usb_ep_autoconf, but 
a little bit later during USB RESET this flag is cleared. 
So, I can't base on this flag.  I think that it's incorrect behavior in gadget core driver or in function drivers.
Maybe we should have additional flag in usb_ep object used during resetting, or usb_ep_autoconf function should be called after each USB RESET. 

I notice that I can based on ep->address it is set also in usb_ep_autoconf and probably is not cleared anywhere.
It's little tricky, but It looks like it works correct. I add some comment for this checking. 
Maybe in feature I will be able to replace it with claimed flag.

Felipe what's your opinion about claimed flag.  It's should be fixed or not ?

Cheers
Pawel

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-02 20:34     ` Pawel Laszczak
@ 2018-12-04  7:11       ` Peter Chen
  2018-12-05  7:19         ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Peter Chen @ 2018-12-04  7:11 UTC (permalink / raw)
  To: pawell
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

On Mon, Dec 3, 2018 at 4:34 AM Pawel Laszczak <pawell@cadence.com> wrote:
>
> >>
> >> Patch adds core.c and core.h file that implements initialization
> >> of platform driver and adds function responsible for selecting,
> >> switching and running appropriate Device/Host mode.
> >>
> >> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> >> ---
> >>  drivers/usb/cdns3/Makefile |   2 +
> >>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
> >>  drivers/usb/cdns3/core.h   | 100 +++++++++
> >>  3 files changed, 515 insertions(+)
> >>  create mode 100644 drivers/usb/cdns3/core.c
> >>  create mode 100644 drivers/usb/cdns3/core.h
> >>
> >> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> >> index dcdd62003c6a..02d25b23c5d3 100644
> >> --- a/drivers/usb/cdns3/Makefile
> >> +++ b/drivers/usb/cdns3/Makefile
> >> @@ -1,3 +1,5 @@
> >> +obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
> >>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
> >>
> >> +cdns3-y                                        := core.o
> >>  cdns3-pci-y                            := cdns3-pci-wrap.o
> >> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> >> new file mode 100644
> >> index 000000000000..f9055d4da67f
> >> --- /dev/null
> >> +++ b/drivers/usb/cdns3/core.c
> >> @@ -0,0 +1,413 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * Cadence USBSS DRD Driver.
> >> + *
> >> + * Copyright (C) 2018 Cadence.
> >> + *
> >
> >Please add NXP copyright too.
>
> Ok, I don't know why I omitted this.
> I know that you are the main author of this file
> Sorry for that.
>
> One additional question. What year I should add in Copyright for NXP?.
> The original year 2017 or I should modified all to 2018.
>
Please use below copyright, thanks.

Copyright 2017-2018 NXP



> >> +       mutex_init(&cdns->mutex);
> >> +
> >> +       cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> >> +       if (IS_ERR(cdns->phy)) {
> >> +               dev_info(dev, "no generic phy found\n");
> >> +               cdns->phy = NULL;
> >> +               /*
> >> +                * fall through here!
> >> +                * if no generic phy found, phy init
> >> +                * should be done under boot!
> >> +                */
> >
> >If the phy driver is defer-probed, it will be here, it is not an error.
> >I think you could have a generic phy driver or usb generic phy driver
> >(drivers/usb/phy/phy-generic.c) even you don't need any operations for
> >PHY. It will be easy for other platforms.
>
> Yes, Roger ask me to modify this fragment. In next version it will look like:
>         cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>         if (IS_ERR(cdns->phy)) {
>                 ret = PTR_ERR(cdns->phy);
>                 if (ret == -ENOSYS || ret == -ENODEV) {
>                         cdns->phy = NULL;
>                 } else if (ret == -EPROBE_DEFER) {
>                         return ret;
>                 } else {
>                         dev_err(dev, "no phy found\n");
>                         goto err0;
>                 }
>         }
>
>         phy_init(cdns->phy);
>
> We are going to use phy driver. I don't know if it correct.
> I don't have experience in this filed.
> We need phy initialization but I don't have testing platform now.
> In most usb drivers I see that there are used usb phy driverd instead phy dirverd.
>

At my CDNS3 platform, there are some USB PHY initialization for register setting
and clock enable. You could add generic PHY driver under: drivers/phy/cadence/.

Above PHY initialization code is OK for me.


> >> +static void __exit cdns3_driver_platform_unregister(void)
> >> +{
> >> +       platform_driver_unregister(&cdns3_driver);
> >> +}
> >> +module_exit(cdns3_driver_platform_unregister);
> >> +
> >> +MODULE_ALIAS("platform:cdns3");
> >> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
> >> +MODULE_LICENSE("GPL v2");
> >> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
> >> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
> >> new file mode 100644
> >> index 000000000000..7c8204fe4d3d
> >> --- /dev/null
> >> +++ b/drivers/usb/cdns3/core.h
> >> @@ -0,0 +1,100 @@
> >> +/* SPDX-License-Identifier: GPL-2.0 */
> >> +/*
> >> + * Cadence USBSS DRD Driver.
> >> + *
> >
> >Header file
> I don't understand. What is wrong ?
> >

The comment for this file

Cadence USBSS DRD Core Header File

Peter

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-23 11:35   ` Roger Quadros
  2018-11-25 12:35     ` Pawel Laszczak
@ 2018-12-04  8:50     ` Peter Chen
  2018-12-04 10:46       ` Roger Quadros
                         ` (2 more replies)
  1 sibling, 3 replies; 85+ messages in thread
From: Peter Chen @ 2018-12-04  8:50 UTC (permalink / raw)
  To: rogerq
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

> > + * Cadence USBSS DRD Driver.
> > + *
> > + * Copyright (C) 2018 Cadence.
> > + *
> > + * Author: Peter Chen <peter.chen@nxp.com>
> > + *         Pawel Laszczak <pawell@cadence.com>
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/kernel.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include "gadget.h"
> > +#include "core.h"
> > +
> > +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> > +{
> > +     WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
> > +     return cdns->roles[cdns->role];
> > +}
> > +
> > +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
> > +{
> > +     int ret;
> > +
> > +     if (role >= CDNS3_ROLE_END)
>
> WARN_ON()?
>
> > +             return 0;
> > +
> > +     if (!cdns->roles[role])
> > +             return -ENXIO;
> > +
> > +     mutex_lock(&cdns->mutex);
> > +     cdns->role = role;
> > +     ret = cdns->roles[role]->start(cdns);
> > +     mutex_unlock(&cdns->mutex);
> > +     return ret;
> > +}
> > +
> > +static inline void cdns3_role_stop(struct cdns3 *cdns)
> > +{
> > +     enum cdns3_roles role = cdns->role;
> > +
> > +     if (role == CDNS3_ROLE_END)
>
> WARN_ON(role >= CNDS3_ROLE_END) ?
>
> > +             return;
> > +
> > +     mutex_lock(&cdns->mutex);
> > +     cdns->roles[role]->stop(cdns);
> > +     cdns->role = CDNS3_ROLE_END;
>
> Why change the role here? You are just stopping the role not changing it.
> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
> if required without error.
>

The current version of this IP has some issues to detect vbus status correctly,
we have to force vbus status accordingly, so we need a status to indicate
vbus disconnection, and add some code to let controller know vbus
removal, in that case, the controller's state machine can be correct.
So, we increase one role 'CDNS3_ROLE_END' to for this purpose.

CDNS3_ROLE_GADGET: gadget mode and VBUS on
CDNS3_ROLE_HOST: host mode and VBUS on
CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.

So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
and need to set role as CDNS3_ROLE_END at ->stop for further handling at
role switch routine.

> > +     mutex_unlock(&cdns->mutex);
> > +}
> > +
> > +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
> > +{
> > +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> > +             //TODO: implements selecting device/host mode
> > +             return CDNS3_ROLE_HOST;
> > +     }
> > +     return cdns->roles[CDNS3_ROLE_HOST]
> > +             ? CDNS3_ROLE_HOST
> > +             : CDNS3_ROLE_GADGET;
>
> Why not just
>         return cdns->role;
>
> I'm wondering if we really need this function.

cdns->role gets from cdns3_get_role, and this API tells role at the runtime.
If both roles are supported, the role is decided by external
conditions, eg, vbus/id
or external connector. If only single role is supported, only one role structure
is allocated, cdns->roles[CDNS3_ROLE_HOST] or cdns->roles[CDNS3_ROLE_GADGET]

> > +}
>
> > +
> > +/**
> > + * cdns3_core_init_role - initialize role of operation
> > + * @cdns: Pointer to cdns3 structure
> > + *
> > + * Returns 0 on success otherwise negative errno
> > + */
> > +static int cdns3_core_init_role(struct cdns3 *cdns)
> > +{
> > +     struct device *dev = cdns->dev;
> > +     enum usb_dr_mode dr_mode;
> > +
> > +     dr_mode = usb_get_dr_mode(dev);
> > +     cdns->role = CDNS3_ROLE_END;
> > +
> > +     /*
> > +      * If driver can't read mode by means of usb_get_dr_mdoe function then
> > +      * chooses mode according with Kernel configuration. This setting
> > +      * can be restricted later depending on strap pin configuration.
> > +      */
> > +     if (dr_mode == USB_DR_MODE_UNKNOWN) {
> > +             if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
> > +                 IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> > +                     dr_mode = USB_DR_MODE_OTG;
> > +             else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
> > +                     dr_mode = USB_DR_MODE_HOST;
> > +             else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> > +                     dr_mode = USB_DR_MODE_PERIPHERAL;
> > +     }
> > +
> > +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
> > +             //TODO: implements host initialization
>
>                 /* TODO: Add host role */ ?
>
> > +     }
> > +
> > +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
> > +             //TODO: implements device initialization
>
>                 /* TODO: Add device role */ ?
>

Yes, it needs to allocate cdns->roles[CDNS3_ROLE_HOST] and
cdns->roles[CDNS3_ROLE_GADGET].

> > +     }
> > +
> > +     if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
> > +             dev_err(dev, "no supported roles\n");
> > +             return -ENODEV;
> > +     }
> > +
> > +     cdns->dr_mode = dr_mode;

Pawel, why dr_mode needs to be introduced?

> > +     return 0;
> > +}
> > +
> > +/**
> > + * cdns3_irq - interrupt handler for cdns3 core device
> > + *
> > + * @irq: irq number for cdns3 core device
> > + * @data: structure of cdns3
> > + *
> > + * Returns IRQ_HANDLED or IRQ_NONE
> > + */
> > +static irqreturn_t cdns3_irq(int irq, void *data)
> > +{
> > +     struct cdns3 *cdns = data;
> > +     irqreturn_t ret = IRQ_NONE;
> > +
> > +     /* Handle device/host interrupt */
> > +     if (cdns->role != CDNS3_ROLE_END)
>
> Is it because of this that you need to set role to END at role_stop?
> I think it is better to add a state variable to struct cdns3_role_driver, so we can
> check if it is active or stopped.
>
> e.g.
>         if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)
>
> > +             ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
> > +
> > +     return ret;
> > +}
> > +

 CDNS3_ROLE_END is introduced from above comments, we don't
need another flag for it.
If cdns->role == CDNS3_ROLE_END, it handles VBUS and ID interrupt.

> > +static void cdns3_remove_roles(struct cdns3 *cdns)
>
> Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?
>

It is planed to called when at ->remove.
> > +{
> > +     //TODO: implements this function
> > +}
>
> > +
> > +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
> > +{
> > +     enum cdns3_roles current_role;
> > +     int ret = 0;
> > +
> > +     current_role = cdns->role;
> > +
> > +     if (role == CDNS3_ROLE_END)
> > +             return 0;
>
> role == END looks like error state. and it should never happen.
> WARN here?
>

See my comments above.

> > +
> > +     dev_dbg(cdns->dev, "Switching role");
> > +
>
> Don't you have to stop the previous role before starting the new role?
>

Yes, it is needed. Pawel may simply some flows to suit his platform.

> > +     ret = cdns3_role_start(cdns, role);
> > +     if (ret) {
> > +             /* Back to current role */
> > +             dev_err(cdns->dev, "set %d has failed, back to %d\n",
> > +                     role, current_role);
> > +             ret = cdns3_role_start(cdns, current_role);
> > +     }
> > +
> > +     return ret;
> > +}
> > +
> > +/**
> > + * cdns3_role_switch - work queue handler for role switch
> > + *
> > + * @work: work queue item structure
> > + *
> > + * Handles below events:
> > + * - Role switch for dual-role devices
> > + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
> > + */
> > +static void cdns3_role_switch(struct work_struct *work)
> > +{
> > +     enum cdns3_roles role = CDNS3_ROLE_END;
> > +     struct cdns3 *cdns;
> > +     bool device, host;
> > +
> > +     cdns = container_of(work, struct cdns3, role_switch_wq);
> > +
> > +     //TODO: implements this functions.
> > +     //host = cdns3_is_host(cdns);
> > +     //device = cdns3_is_device(cdns);
> > +     host = 1;
> > +     device = 0;
> > +
> > +     if (host)
> > +             role = CDNS3_ROLE_HOST;
> > +     else if (device)
> > +             role = CDNS3_ROLE_GADGET;
> > +
> > +     if (cdns->desired_dr_mode == cdns->current_dr_mode &&
> > +         cdns->role == role)
> > +             return;
> > +
>
> I think all the below code can be moved to cdns3_do_role_switch().
>
> > +     pm_runtime_get_sync(cdns->dev);
> > +     cdns3_role_stop(cdns);
> > +
> > +     if (host) {
> > +             if (cdns->roles[CDNS3_ROLE_HOST])
> > +                     cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
> > +             pm_runtime_put_sync(cdns->dev);
> > +             return;
> > +     }
> > +
> > +     if (device)
> > +             cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
> > +     else
> > +             cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
> > +
> > +     pm_runtime_put_sync(cdns->dev);
> > +}
> > +
> > +/**
> > + * cdns3_probe - probe for cdns3 core device
> > + * @pdev: Pointer to cdns3 core platform device
> > + *
> > + * Returns 0 on success otherwise negative errno
> > + */
> > +static int cdns3_probe(struct platform_device *pdev)
> > +{
> > +     struct device *dev = &pdev->dev;
> > +     struct resource *res;
> > +     struct cdns3 *cdns;
> > +     void __iomem *regs;
> > +     int ret;
> > +
> > +     cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
> > +     if (!cdns)
> > +             return -ENOMEM;
> > +
> > +     cdns->dev = dev;
> > +
> > +     platform_set_drvdata(pdev, cdns);
> > +
> > +     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> > +     if (!res) {
> > +             dev_err(dev, "missing IRQ\n");
> > +             return -ENODEV;
> > +     }
> > +     cdns->irq = res->start;
> > +
> > +     /*
> > +      * Request memory region
> > +      * region-0: xHCI
> > +      * region-1: Peripheral
> > +      * region-2: OTG registers
> > +      */
>
> The memory region order is different from the dt-binding.
> There it is OTG, host(xhci), device (peripheral).
>
> > +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +     regs = devm_ioremap_resource(dev, res);
> > +
> > +     if (IS_ERR(regs))
> > +             return PTR_ERR(regs);
> > +     cdns->xhci_regs = regs;
> > +     cdns->xhci_res = res;
> > +
> > +     res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> > +     regs = devm_ioremap_resource(dev, res);
> > +     if (IS_ERR(regs))
> > +             return PTR_ERR(regs);
> > +     cdns->dev_regs  = regs;
> > +
> > +     res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> > +     regs = devm_ioremap_resource(dev, res);
> > +     if (IS_ERR(regs))
> > +             return PTR_ERR(regs);
> > +     cdns->otg_regs = regs;
> > +
> > +     mutex_init(&cdns->mutex);
> > +
> > +     cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>
> "cdns3,usbphy" is not documented in dt-binding.
>
> > +     if (IS_ERR(cdns->phy)) {
> > +             dev_info(dev, "no generic phy found\n");
> > +             cdns->phy = NULL;
> > +             /*
> > +              * fall through here!
> > +              * if no generic phy found, phy init
> > +              * should be done under boot!
> > +              */
>
> No you shouldn't fall through always if it is an error condition.
> Something like this should work better.
>
>         if (IS_ERR(cnds->phy)) {
>                 ret = PTR_ERR(cdns->phy);
>                 if (ret == -ENOSYS || ret == -ENODEV) {
>                         cdns->phy = NULL;
>                 } else if (ret == -EPROBE_DEFER) {
>                         return ret;
>                 } else {
>                         dev_err(dev, "no phy found\n");
>                         goto err0;
>                 }
>         }
>
> So if PHY was provided in DT, and PHY support/drivers is present
> and error condition means something is wrong and we have to error out.
>
> > +     } else {
> > +             phy_init(cdns->phy);
> > +     }
>
> You can do phy_init() outside the else.
>
> > +
> > +     ret = cdns3_core_init_role(cdns);
> > +     if (ret)
> > +             goto err1;
> > +
> > +     INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
> > +     if (ret)
> > +             goto err2;
> > +
> > +     if (ret)
> > +             goto err2;
> > +
> > +     cdns->role = cdns3_get_role(cdns);
>
> I think this should move to cdns3_core_init_role().
>

I agree.

> > +
> > +     ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
> > +                            dev_name(dev), cdns);
> > +
> > +     if (ret)
> > +             goto err2;
>
> How about moving request_irq to before cdsn3_core_init_role()?
>
> Then you can move cdns3_role_start() as well to core_init_role().
>

Usually, we request irq after hardware initialization has finished, if not,
there may unexpected interrupt.

Peter

> > +
> > +     ret = cdns3_role_start(cdns, cdns->role);
> > +     if (ret) {
> > +             dev_err(dev, "can't start %s role\n",
> > +                     cdns3_get_current_role_driver(cdns)->name);
> > +             goto err2;
> > +     }
> > +
> > +     device_set_wakeup_capable(dev, true);
> > +     pm_runtime_set_active(dev);
> > +     pm_runtime_enable(dev);
> > +
> > +     /*
> > +      * The controller needs less time between bus and controller suspend,
> > +      * and we also needs a small delay to avoid frequently entering low
> > +      * power mode.
> > +      */
> > +     pm_runtime_set_autosuspend_delay(dev, 20);
> > +     pm_runtime_mark_last_busy(dev);
> > +     pm_runtime_use_autosuspend(dev);
> > +     dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
> > +
> > +     return 0;
> > +
> > +err2:
> > +     cdns3_remove_roles(cdns);
> > +err1:
>
> phy_exit() ?
>
> > +     return ret;
> > +}
> > +
> > +/**
> > + * cdns3_remove - unbind drd driver and clean up
> > + * @pdev: Pointer to Linux platform device
> > + *
> > + * Returns 0 on success otherwise negative errno
> > + */
> > +static int cdns3_remove(struct platform_device *pdev)
> > +{
> > +     struct cdns3 *cdns = platform_get_drvdata(pdev);
> > +
> > +     pm_runtime_get_sync(&pdev->dev);
> > +     pm_runtime_disable(&pdev->dev);
> > +     pm_runtime_put_noidle(&pdev->dev);
> > +     cdns3_remove_roles(cdns);
>
> phy_exit() ?
>
> > +
> > +     return 0;
> > +}
> > +
> > +#ifdef CONFIG_OF
> > +static const struct of_device_id of_cdns3_match[] = {
> > +     { .compatible = "cdns,usb3" },
> > +     { },
> > +};
> > +MODULE_DEVICE_TABLE(of, of_cdns3_match);
> > +#endif
> > +
> > +#ifdef CONFIG_PM
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int cdns3_suspend(struct device *dev)
> > +{
> > +     //TODO: implements this function
> > +     return 0;
> > +}
> > +
> > +static int cdns3_resume(struct device *dev)
> > +{
> > +     //TODO: implements this function
> > +     return 0;
> > +}
> > +#endif /* CONFIG_PM_SLEEP */
> > +static int cdns3_runtime_suspend(struct device *dev)
> > +{    //TODO: implements this function
> > +     return 0;
> > +}
> > +
> > +static int cdns3_runtime_resume(struct device *dev)
> > +{
> > +     //TODO: implements this function
> > +     return 0;
> > +}
> > +#endif /* CONFIG_PM */
> > +
> > +static const struct dev_pm_ops cdns3_pm_ops = {
> > +     SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
> > +     SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
> > +};
> > +
> > +static struct platform_driver cdns3_driver = {
> > +     .probe          = cdns3_probe,
> > +     .remove         = cdns3_remove,
> > +     .driver         = {
> > +             .name   = "cdns-usb3",
> > +             .of_match_table = of_match_ptr(of_cdns3_match),
> > +             .pm     = &cdns3_pm_ops,
> > +     },
> > +};
> > +
> > +static int __init cdns3_driver_platform_register(void)
> > +{
> > +     return platform_driver_register(&cdns3_driver);
> > +}
> > +module_init(cdns3_driver_platform_register);
> > +
> > +static void __exit cdns3_driver_platform_unregister(void)
> > +{
> > +     platform_driver_unregister(&cdns3_driver);
> > +}
> > +module_exit(cdns3_driver_platform_unregister);
> > +
> > +MODULE_ALIAS("platform:cdns3");
> > +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
> > diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
> > new file mode 100644
> > index 000000000000..7c8204fe4d3d
> > --- /dev/null
> > +++ b/drivers/usb/cdns3/core.h
> > @@ -0,0 +1,100 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Cadence USBSS DRD Driver.
> > + *
> > + * Copyright (C) 2017 NXP
> > + * Copyright (C) 2018 Cadence.
> > + *
> > + * Authors: Peter Chen <peter.chen@nxp.com>
> > + *          Pawel Laszczak <pawell@cadence.com>
> > + */
> > +#include <linux/usb/otg.h>
> > +
> > +#ifndef __LINUX_CDNS3_CORE_H
> > +#define __LINUX_CDNS3_CORE_H
> > +
> > +struct cdns3;
> > +enum cdns3_roles {
> > +     CDNS3_ROLE_HOST = 0,
> > +     CDNS3_ROLE_GADGET,
> > +     CDNS3_ROLE_END,
> > +};
> > +
> > +/**
> > + * struct cdns3_role_driver - host/gadget role driver
> > + * @start: start this role
> > + * @stop: stop this role
> > + * @suspend: suspend callback for this role
> > + * @resume: resume callback for this role
> > + * @irq: irq handler for this role
> > + * @name: role name string (host/gadget)
> > + */
> > +struct cdns3_role_driver {
> > +     int (*start)(struct cdns3 *cdns);
> > +     void (*stop)(struct cdns3 *cdns);
> > +     int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
> > +     int (*resume)(struct cdns3 *cdns, bool hibernated);
> > +     irqreturn_t (*irq)(struct cdns3 *cdns);
> > +     const char *name;
> > +};
> > +
> > +#define CDNS3_NUM_OF_CLKS    5
> > +/**
> > + * struct cdns3 - Representation of Cadence USB3 DRD controller.
> > + * @dev: pointer to Cadence device struct
> > + * @xhci_regs: pointer to base of xhci registers
> > + * @xhci_res: the resource for xhci
> > + * @dev_regs: pointer to base of dev registers
> > + * @otg_regs: pointer to base of otg registers
> > + * @irq: irq number for controller
> > + * @roles: array of supported roles for this controller
> > + * @role: current role
> > + * @host_dev: the child host device pointer for cdns3 core
> > + * @gadget_dev: the child gadget device pointer for cdns3 core
> > + * @usb: phy for this controller
> > + * @role_switch_wq: work queue item for role switch
> > + * @in_lpm: the controller in low power mode
> > + * @wakeup_int: the wakeup interrupt
> > + * @mutex: the mutex for concurrent code at driver
> > + * @dr_mode: supported mode of operation it can be only Host, only Device
> > + *           or OTG mode that allow to switch between Device and Host mode.
> > + *           This field based on hardware configuration and cant't be changed.
>
> But dr_mode can be forced in device-tree. So it isn't really only hardware configuration.
>
> > + * @current_dr_role: current mode of operation when in dual-role mode
> > + * @desired_dr_role: desired mode of operation when in dual-role mode.
> > + *           This value can be changed during runtime.
> > + *           Available options depends on  dr_mode:
> > + *           dr_mode                 |  desired_dr_role and current_dr_role
> > + *           ----------------------------------------------------------------
> > + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
> > + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
> > + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
> > + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
> > + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
>
> Do you need to update the right hand side to reflect ROLEs instead of MODE?
>
> > + *
> > + *           Desired_dr_role can be changed by means of debugfs.
> > + * @root: debugfs root folder pointer
> > + */
> > +struct cdns3 {
> > +     struct device                   *dev;
> > +     void __iomem                    *xhci_regs;
> > +     struct resource                 *xhci_res;
> > +     struct cdns3_usb_regs __iomem   *dev_regs;
> > +     struct cdns3_otg_regs           *otg_regs;
> > +     int irq;
> > +     struct cdns3_role_driver        *roles[CDNS3_ROLE_END];
> > +     enum cdns3_roles                role;
> > +     struct device                   *host_dev;
> > +     struct device                   *gadget_dev;
> > +     struct phy                      *phy;
> > +     struct work_struct              role_switch_wq;
> > +     int                             in_lpm:1;
> > +     int                             wakeup_int:1;
> > +     /* mutext used in workqueue*/
> > +     struct mutex                    mutex;
> > +     enum usb_dr_mode                dr_mode;
> > +     enum usb_dr_mode                current_dr_mode;
> > +     enum usb_dr_mode                desired_dr_mode;
> > +     struct dentry                   *root;
> > +};
> > +
> > +#endif /* __LINUX_CDNS3_CORE_H */
> >
>
> cheers,
> -roger
> --
> Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
> Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-11-25 12:35     ` Pawel Laszczak
@ 2018-12-04  9:09       ` Peter Chen
  2018-12-06  7:00         ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Peter Chen @ 2018-12-04  9:09 UTC (permalink / raw)
  To: pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

> Hi Roger
>
> >On 18/11/18 12:09, Pawel Laszczak wrote:
> >> Patch adds core.c and core.h file that implements initialization
> >> of platform driver and adds function responsible for selecting,
> >> switching and running appropriate Device/Host mode.
> >>
> >> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> >> ---
> >>  drivers/usb/cdns3/Makefile |   2 +
> >>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
> >>  drivers/usb/cdns3/core.h   | 100 +++++++++
> >>  3 files changed, 515 insertions(+)
> >>  create mode 100644 drivers/usb/cdns3/core.c
> >>  create mode 100644 drivers/usb/cdns3/core.h
> >>
> >> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> >> index dcdd62003c6a..02d25b23c5d3 100644
> >> --- a/drivers/usb/cdns3/Makefile
> >> +++ b/drivers/usb/cdns3/Makefile
> >> @@ -1,3 +1,5 @@
> >> +obj-$(CONFIG_USB_CDNS3)                     += cdns3.o
> >>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)    += cdns3-pci.o
> >>
> >> +cdns3-y                                     := core.o
> >>  cdns3-pci-y                         := cdns3-pci-wrap.o
> >> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> >> new file mode 100644
> >> index 000000000000..f9055d4da67f
> >> --- /dev/null
> >> +++ b/drivers/usb/cdns3/core.c
> >> @@ -0,0 +1,413 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * Cadence USBSS DRD Driver.
> >> + *
> >> + * Copyright (C) 2018 Cadence.
> >> + *
> >> + * Author: Peter Chen <peter.chen@nxp.com>
> >> + *         Pawel Laszczak <pawell@cadence.com>
> >> + */
> >> +
> >> +#include <linux/module.h>
> >> +#include <linux/kernel.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/interrupt.h>
> >> +#include <linux/io.h>
> >> +#include <linux/pm_runtime.h>
> >> +
> >> +#include "gadget.h"
> >> +#include "core.h"
> >> +
> >> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> >> +{
> >> +    WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
> >> +    return cdns->roles[cdns->role];
> >> +}
> >> +
> >> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
> >> +{
> >> +    int ret;
> >> +
> >> +    if (role >= CDNS3_ROLE_END)
> >
> >WARN_ON()?
> I agree.
> >
> >> +            return 0;
> >> +
> >> +    if (!cdns->roles[role])
> >> +            return -ENXIO;
> >> +
> >> +    mutex_lock(&cdns->mutex);
> >> +    cdns->role = role;
> >> +    ret = cdns->roles[role]->start(cdns);
> >> +    mutex_unlock(&cdns->mutex);
> >> +    return ret;
> >> +}
> >> +
> >> +static inline void cdns3_role_stop(struct cdns3 *cdns)
> >> +{
> >> +    enum cdns3_roles role = cdns->role;
> >> +
> >> +    if (role == CDNS3_ROLE_END)
> >
> >WARN_ON(role >= CNDS3_ROLE_END) ?
> I agree
> >
> >> +            return;
> >> +
> >> +    mutex_lock(&cdns->mutex);
> >> +    cdns->roles[role]->stop(cdns);
> >> +    cdns->role = CDNS3_ROLE_END;
> >
> >Why change the role here? You are just stopping the role not changing it.
> >I think cdns->role should remain unchanged, so we can call cdns3_role_start()
> >if required without error.
>
> This line is unnecessary.
>
> >
> >> +    mutex_unlock(&cdns->mutex);
> >> +}
> >> +
> >> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
> >> +{
> >> +    if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> >> +            //TODO: implements selecting device/host mode
> >> +            return CDNS3_ROLE_HOST;
> >> +    }
> >> +    return cdns->roles[CDNS3_ROLE_HOST]
> >> +            ? CDNS3_ROLE_HOST
> >> +            : CDNS3_ROLE_GADGET;
> >
> >Why not just
> >       return cdns->role;
> >
> >I'm wondering if we really need this function
>
> TODO will look likie:
>                 if (cdns3_is_host(cdns))
>                         return CDNS3_ROLE_HOST;
>                 if (cdns3_is_device(cdns))
>                         return CDNS3_ROLE_GADGET;
>
> Function selects initial role. Before invoking it the role is unknown.
> I think that function name should be  changed because current name can be misleading.
>
> I will change it to cdns3_get_initial_role.
> .
> >> +}
> >
> >> +
> >> +/**
> >> + * cdns3_core_init_role - initialize role of operation
> >> + * @cdns: Pointer to cdns3 structure
> >> + *
> >> + * Returns 0 on success otherwise negative errno
> >> + */
> >> +static int cdns3_core_init_role(struct cdns3 *cdns)
> >> +{
> >> +    struct device *dev = cdns->dev;
> >> +    enum usb_dr_mode dr_mode;
> >> +
> >> +    dr_mode = usb_get_dr_mode(dev);
> >> +    cdns->role = CDNS3_ROLE_END;
> >> +
> >> +    /*
> >> +     * If driver can't read mode by means of usb_get_dr_mdoe function then
> >> +     * chooses mode according with Kernel configuration. This setting
> >> +     * can be restricted later depending on strap pin configuration.
> >> +     */
> >> +    if (dr_mode == USB_DR_MODE_UNKNOWN) {
> >> +            if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
> >> +                IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> >> +                    dr_mode = USB_DR_MODE_OTG;
> >> +            else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
> >> +                    dr_mode = USB_DR_MODE_HOST;
> >> +            else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
> >> +                    dr_mode = USB_DR_MODE_PERIPHERAL;
> >> +    }
> >> +
> >> +    if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
> >> +            //TODO: implements host initialization
> >
> >               /* TODO: Add host role */ ?
> >
> >> +    }
> >> +
> >> +    if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
> >> +            //TODO: implements device initialization
> >
> >               /* TODO: Add device role */ ?
> >
> >> +    }
> >> +
> >> +    if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
> >> +            dev_err(dev, "no supported roles\n");
> >> +            return -ENODEV;
> >> +    }
> >> +
> >> +    cdns->dr_mode = dr_mode;
> >> +    return 0;
> >> +}
> >> +
> >> +/**
> >> + * cdns3_irq - interrupt handler for cdns3 core device
> >> + *
> >> + * @irq: irq number for cdns3 core device
> >> + * @data: structure of cdns3
> >> + *
> >> + * Returns IRQ_HANDLED or IRQ_NONE
> >> + */
> >> +static irqreturn_t cdns3_irq(int irq, void *data)
> >> +{
> >> +    struct cdns3 *cdns = data;
> >> +    irqreturn_t ret = IRQ_NONE;
> >> +
> >> +    /* Handle device/host interrupt */
> >> +    if (cdns->role != CDNS3_ROLE_END)
> >
> >Is it because of this that you need to set role to END at role_stop?
> >I think it is better to add a state variable to struct cdns3_role_driver, so we can
> >check if it is active or stopped.
> >
> >e.g.
> >       if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)
>
> Ok, I will do it in this way.
> >> +            ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
> >> +
> >> +    return ret;
> >> +}
> >> +
> >> +static void cdns3_remove_roles(struct cdns3 *cdns)
> >
> >Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?
>
> Sounds better.
> I also change cdns3_host_remove to cdns3_host_exit and
> cdns3_gadget_remove to cdns3_gadget_exit.
> >
> >> +{
> >> +    //TODO: implements this function
> >> +}
> >
> >> +
> >> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
> >> +{
> >> +    enum cdns3_roles current_role;
> >> +    int ret = 0;
> >> +
> >> +    current_role = cdns->role;
> >> +
> >> +    if (role == CDNS3_ROLE_END)
> >> +            return 0;
> >
> >role == END looks like error state. and it should never happen.
> >WARN here?
>
> Ok, will be changed.
> >

Please consider my comments which replied to Roger just now.

> >> +
> >> +    dev_dbg(cdns->dev, "Switching role");
> >> +
> >
> >Don't you have to stop the previous role before starting the new role?
> >
> >> +    ret = cdns3_role_start(cdns, role);
> >> +    if (ret) {
> >> +            /* Back to current role */
> >> +            dev_err(cdns->dev, "set %d has failed, back to %d\n",
> >> +                    role, current_role);
> >> +            ret = cdns3_role_start(cdns, current_role);
> >> +    }
> >> +
> >> +    return ret;
> >> +}
> >> +
> >> +/**
> >> + * cdns3_role_switch - work queue handler for role switch
> >> + *
> >> + * @work: work queue item structure
> >> + *
> >> + * Handles below events:
> >> + * - Role switch for dual-role devices
> >> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
> >> + */
> >> +static void cdns3_role_switch(struct work_struct *work)
> >> +{
> >> +    enum cdns3_roles role = CDNS3_ROLE_END;
> >> +    struct cdns3 *cdns;
> >> +    bool device, host;
> >> +
> >> +    cdns = container_of(work, struct cdns3, role_switch_wq);
> >> +
> >> +    //TODO: implements this functions.
> >> +    //host = cdns3_is_host(cdns);
> >> +    //device = cdns3_is_device(cdns);
> >> +    host = 1;
> >> +    device = 0;
> >> +
> >> +    if (host)
> >> +            role = CDNS3_ROLE_HOST;
> >> +    else if (device)
> >> +            role = CDNS3_ROLE_GADGET;
> >> +
> >> +    if (cdns->desired_dr_mode == cdns->current_dr_mode &&
> >> +        cdns->role == role)
> >> +            return;
> >> +
> >
> >I think all the below code can be moved to cdns3_do_role_switch().
>
> Yes, I agree with you. cdns3_role_stop  should be in cdns3_do_role_switch.
>
> >> +    pm_runtime_get_sync(cdns->dev);
> >> +    cdns3_role_stop(cdns);
> >> +
> >> +    if (host) {
> >> +            if (cdns->roles[CDNS3_ROLE_HOST])
> >> +                    cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
> >> +            pm_runtime_put_sync(cdns->dev);
> >> +            return;
> >> +    }
> >> +
> >> +    if (device)
> >> +            cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
> >> +    else
> >> +            cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
> >> +
> >> +    pm_runtime_put_sync(cdns->dev);
> >> +}
> >> +
> >> +/**
> >> + * cdns3_probe - probe for cdns3 core device
> >> + * @pdev: Pointer to cdns3 core platform device
> >> + *
> >> + * Returns 0 on success otherwise negative errno
> >> + */
> >> +static int cdns3_probe(struct platform_device *pdev)
> >> +{
> >> +    struct device *dev = &pdev->dev;
> >> +    struct resource *res;
> >> +    struct cdns3 *cdns;
> >> +    void __iomem *regs;
> >> +    int ret;
> >> +
> >> +    cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
> >> +    if (!cdns)
> >> +            return -ENOMEM;
> >> +
> >> +    cdns->dev = dev;
> >> +
> >> +    platform_set_drvdata(pdev, cdns);
> >> +
> >> +    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> >> +    if (!res) {
> >> +            dev_err(dev, "missing IRQ\n");
> >> +            return -ENODEV;
> >> +    }
> >> +    cdns->irq = res->start;
> >> +
> >> +    /*
> >> +     * Request memory region
> >> +     * region-0: xHCI
> >> +     * region-1: Peripheral
> >> +     * region-2: OTG registers
> >> +     */
> >
> >The memory region order is different from the dt-binding.
> >There it is OTG, host(xhci), device (peripheral).
>
> I corrected dt-binding.
> >
> >> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >> +    regs = devm_ioremap_resource(dev, res);
> >> +
> >> +    if (IS_ERR(regs))
> >> +            return PTR_ERR(regs);
> >> +    cdns->xhci_regs = regs;
> >> +    cdns->xhci_res = res;
> >> +
> >> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> >> +    regs = devm_ioremap_resource(dev, res);
> >> +    if (IS_ERR(regs))
> >> +            return PTR_ERR(regs);
> >> +    cdns->dev_regs  = regs;
> >> +
> >> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> >> +    regs = devm_ioremap_resource(dev, res);
> >> +    if (IS_ERR(regs))
> >> +            return PTR_ERR(regs);
> >> +    cdns->otg_regs = regs;
> >> +
> >> +    mutex_init(&cdns->mutex);
> >> +
> >> +    cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> >
> >"cdns3,usbphy" is not documented in dt-binding.
>
> I assume that I should add  to dt-binding (cdns3-usb.txt) something like:
>  - phys: reference to the USB PHY
>  - phy-names: name of the USB PHY, should be " cdns3,usbphy "
>
> >
> >> +    if (IS_ERR(cdns->phy)) {
> >> +            dev_info(dev, "no generic phy found\n");
> >> +            cdns->phy = NULL;
> >> +            /*
> >> +             * fall through here!
> >> +             * if no generic phy found, phy init
> >> +             * should be done under boot!
> >> +             */
> >
> >No you shouldn't fall through always if it is an error condition.
> >Something like this should work better.
> >
> >        if (IS_ERR(cnds->phy)) {
> >                ret = PTR_ERR(cdns->phy);
> >                if (ret == -ENOSYS || ret == -ENODEV) {
> >                        cdns->phy = NULL;
> >                } else if (ret == -EPROBE_DEFER) {
> >                        return ret;
> >                } else {
> >                        dev_err(dev, "no phy found\n");
> >                        goto err0;
> >                }
> >        }
> >
> >So if PHY was provided in DT, and PHY support/drivers is present
> >and error condition means something is wrong and we have to error out.
> >
> >> +    } else {
> >> +            phy_init(cdns->phy);
> >> +    }
> >
> >You can do phy_init() outside the else.
> >
> Thank you for explanation. I will correct this.
> >> +
> >> +    ret = cdns3_core_init_role(cdns);
> >> +    if (ret)
> >> +            goto err1;
> >> +
> >> +    INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
> >> +    if (ret)
> >> +            goto err2;
> >> +
> >> +    if (ret)
> >> +            goto err2;
> >> +
> >> +    cdns->role = cdns3_get_role(cdns);
> >
> >I think this should move to cd I'll have a some though on ns3_core_init_role().
>
> Ok, I will do it.
> >
> >> +
> >> +    ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
> >> +                           dev_name(dev), cdns);
> >> +
> >> +    if (ret)
> >> +            goto err2;
> >
> >How about moving request_irq to before cdsn3_core_init_role()?
> >
> >Then you can move cdns3_role_start() as well to core_init_role().
> I'll give it  some though on it, but probably I will probably have to change little other function..
> So the new order should look like this:
>
> cdns3_drd_init
> devm_request_irq
> cdns3_core_init_role, cdns3_get_role, cdns3_role_start
>
> >
> >> +
> >> +    ret = cdns3_role_start(cdns, cdns->role);
> >> +    if (ret) {
> >> +            dev_err(dev, "can't start %s role\n",
> >> +                    cdns3_get_current_role_driver(cdns)->name);
> >> +            goto err2;
> >> +    }
> >> +
> >> +    device_set_wakeup_capable(dev, true);
> >> +    pm_runtime_set_active(dev);
> >> +    pm_runtime_enable(dev);
> >> +
> >> +    /*
> >> +     * The controller needs less time between bus and controller suspend,
> >> +     * and we also needs a small delay to avoid frequently entering low
> >> +     * power mode.
> >> +     */
> >> +    pm_runtime_set_autosuspend_delay(dev, 20);
> >> +    pm_runtime_mark_last_busy(dev);
> >> +    pm_runtime_use_autosuspend(dev);
> >> +    dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
> >> +
> >> +    return 0;
> >> +
> >> +err2:
> >> +    cdns3_remove_roles(cdns);
> >> +err1:
> >
> >phy_exit() ?
> I will add.
> >
> >> +    return ret;
> >> +}
> >> +
> >> +/**
> >> + * cdns3_remove - unbind drd driver and clean up
> >> + * @pdev: Pointer to Linux platform device
> >> + *
> >> + * Returns 0 on success otherwise negative errno
> >> + */
> >> +static int cdns3_remove(struct platform_device *pdev)
> >> +{
> >> +    struct cdns3 *cdns = platform_get_drvdata(pdev);
> >> +
> >> +    pm_runtime_get_sync(&pdev->dev);
> >> +    pm_runtime_disable(&pdev->dev);
> >> +    pm_runtime_put_noidle(&pdev->dev);
> >> +    cdns3_remove_roles(cdns);
> >
> >phy_exit() ?
> I will add.
> >
> >> +
> >> +    return 0;
> >> +}
> >> +
> >> +#ifdef CONFIG_OF
> >> +static const struct of_device_id of_cdns3_match[] = {
> >> +    { .compatible = "cdns,usb3" },
> >> +    { },
> >> +};
> >> +MODULE_DEVICE_TABLE(of, of_cdns3_match);
> >> +#endif
> >> +
> >> +#ifdef CONFIG_PM
> >> +
> >> +#ifdef CONFIG_PM_SLEEP
> >> +static int cdns3_suspend(struct device *dev)
> >> +{
> >> +    //TODO: implements this function
> >> +    return 0;
> >> +}
> >> +
> >> +static int cdns3_resume(struct device *dev)
> >> +{
> >> +    //TODO: implements this function
> >> +    return 0;
> >> +}
> >> +#endif /* CONFIG_PM_SLEEP */
> >> +static int cdns3_runtime_suspend(struct device *dev)
> >> +{   //TODO: implements this function
> >> +    return 0;
> >> +}
> >> +
> >> +static int cdns3_runtime_resume(struct device *dev)
> >> +{
> >> +    //TODO: implements this function
> >> +    return 0;
> >> +}
> >> +#endif /* CONFIG_PM */
> >> +
> >> +static const struct dev_pm_ops cdns3_pm_ops = {
> >> +    SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
> >> +    SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
> >> +};
> >> +
> >> +static struct platform_driver cdns3_driver = {
> >> +    .probe          = cdns3_probe,
> >> +    .remove         = cdns3_remove,
> >> +    .driver         = {
> >> +            .name   = "cdns-usb3",
> >> +            .of_match_table = of_match_ptr(of_cdns3_match),
> >> +            .pm     = &cdns3_pm_ops,
> >> +    },
> >> +};
> >> +
> >> +static int __init cdns3_driver_platform_register(void)
> >> +{
> >> +    return platform_driver_register(&cdns3_driver);
> >> +}
> >> +module_init(cdns3_driver_platform_register);
> >> +
> >> +static void __exit cdns3_driver_platform_unregister(void)
> >> +{
> >> +    platform_driver_unregister(&cdns3_driver);
> >> +}
> >> +module_exit(cdns3_driver_platform_unregister);
> >> +
> >> +MODULE_ALIAS("platform:cdns3");
> >> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
> >> +MODULE_LICENSE("GPL v2");
> >> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
> >> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
> >> new file mode 100644
> >> index 000000000000..7c8204fe4d3d
> >> --- /dev/null
> >> +++ b/drivers/usb/cdns3/core.h
> >> @@ -0,0 +1,100 @@
> >> +/* SPDX-License-Identifier: GPL-2.0 */
> >> +/*
> >> + * Cadence USBSS DRD Driver.
> >> + *
> >> + * Copyright (C) 2017 NXP
> >> + * Copyright (C) 2018 Cadence.
> >> + *
> >> + * Authors: Peter Chen <peter.chen@nxp.com>
> >> + *          Pawel Laszczak <pawell@cadence.com>
> >> + */
> >> +#include <linux/usb/otg.h>
> >> +
> >> +#ifndef __LINUX_CDNS3_CORE_H
> >> +#define __LINUX_CDNS3_CORE_H
> >> +
> >> +struct cdns3;
> >> +enum cdns3_roles {
> >> +    CDNS3_ROLE_HOST = 0,
> >> +    CDNS3_ROLE_GADGET,
> >> +    CDNS3_ROLE_END,
> >> +};
> >> +
> >> +/**
> >> + * struct cdns3_role_driver - host/gadget role driver
> >> + * @start: start this role
> >> + * @stop: stop this role
> >> + * @suspend: suspend callback for this role
> >> + * @resume: resume callback for this role
> >> + * @irq: irq handler for this role
> >> + * @name: role name string (host/gadget)
> >> + */
> >> +struct cdns3_role_driver {
> >> +    int (*start)(struct cdns3 *cdns);
> >> +    void (*stop)(struct cdns3 *cdns);
> >> +    int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
> >> +    int (*resume)(struct cdns3 *cdns, bool hibernated);
> >> +    irqreturn_t (*irq)(struct cdns3 *cdns);
> >> +    const char *name;
> >> +};
> >> +
> >> +#define CDNS3_NUM_OF_CLKS   5
> >> +/**
> >> + * struct cdns3 - Representation of Cadence USB3 DRD controller.
> >> + * @dev: pointer to Cadence device struct
> >> + * @xhci_regs: pointer to base of xhci registers
> >> + * @xhci_res: the resource for xhci
> >> + * @dev_regs: pointer to base of dev registers
> >> + * @otg_regs: pointer to base of otg registers
> >> + * @irq: irq number for controller
> >> + * @roles: array of supported roles for this controller
> >> + * @role: current role
> >> + * @host_dev: the child host device pointer for cdns3 core
> >> + * @gadget_dev: the child gadget device pointer for cdns3 core
> >> + * @usb: phy for this controller
> >> + * @role_switch_wq: work queue item for role switch
> >> + * @in_lpm: the controller in low power mode
> >> + * @wakeup_int: the wakeup interrupt
> >> + * @mutex: the mutex for concurrent code at driver
> >> + * @dr_mode: supported mode of operation it can be only Host, only Device
> >> + *           or OTG mode that allow to switch between Device and Host mode.
> >> + *           This field based on hardware configuration and cant't be changed.
> >
> >But dr_mode can be forced in device-tree. So it isn't really only hardware configuration.
> Right, I added dr_mode to dt-binding , so we have STRAP bits in registers and additionally
> optional dr_mode in device-tree. Driver should take into account this two options.
> I will remove this line.
>
> >
> >> + * @current_dr_role: current mode of operation when in dual-role mode
> >> + * @desired_dr_role: desired mode of operation when in dual-role mode.
> >> + *           This value can be changed during runtime.
> >> + *           Available options depends on  dr_mode:
> >> + *           dr_mode                 |  desired_dr_role and current_dr_role
> >> + *           ----------------------------------------------------------------
> >> + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
> >> + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
> >> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
> >> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
> >> + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
> >
> >Do you need to update the right hand side to reflect ROLEs instead of MODE?
>
> I see that there are incorrect name. There should be mode instead role.
> In structure below the names are correct.
> >

Usually, we have two type of roles: init role and current role
init role is decided by dr_mode from firmware, hardware setting and
kernel configuration together.
It is decided at ->probe, if above three settings haveconflict, we
need show an error.
current role is decided at run time, and only used at dual-role mode.
For peripheral-only and host-only application, the current role equals
to init role.

Peter

> >> + *
> >> + *           Desired_dr_role can be changed by means of debugfs.
> >> + * @root: debugfs root folder pointer
> >> + */
> >> +struct cdns3 {
> >> +    struct device                   *dev;
> >> +    void __iomem                    *xhci_regs;
> >> +    struct resource                 *xhci_res;
> >> +    struct cdns3_usb_regs __iomem   *dev_regs;
> >> +    struct cdns3_otg_regs           *otg_regs;
> >> +    int irq;
> >> +    struct cdns3_role_driver        *roles[CDNS3_ROLE_END];
> >> +    enum cdns3_roles                role;
> >> +    struct device                   *host_dev;
> >> +    struct device                   *gadget_dev;
> >> +    struct phy                      *phy;
> >> +    struct work_struct              role_switch_wq;
> >> +    int                             in_lpm:1;
> >> +    int                             wakeup_int:1;
> >> +    /* mutext used in workqueue*/
> >> +    struct mutex                    mutex;
> >> +    enum usb_dr_mode                dr_mode;
> >> +    enum usb_dr_mode                current_dr_mode;
> >> +    enum usb_dr_mode                desired_dr_mode;
> >> +    struct dentry                   *root;
> >> +};
> >> +
> >> +#endif /* __LINUX_CDNS3_CORE_H */
> >>
> >
> >cheers,
> >-roger
> >--
> >Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
> >Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
>
> Thank for all your comments,
> Cheers,
> Pawel

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

* Re: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-11-18 10:09 ` [RFC PATCH v2 05/15] usb:cdns3: Added DRD support Pawel Laszczak
  2018-11-23 14:51   ` Roger Quadros
@ 2018-12-04  9:18   ` Peter Chen
  2018-12-06  7:25     ` Pawel Laszczak
  1 sibling, 1 reply; 85+ messages in thread
From: Peter Chen @ 2018-12-04  9:18 UTC (permalink / raw)
  To: pawell
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

On Sun, Nov 18, 2018 at 6:13 PM Pawel Laszczak <pawell@cadence.com> wrote:
>
> Patch adds supports for detecting Host/Device mode.
> Controller has additional OTG register that allow
> implement even whole OTG functionality.
> At this moment patch adds support only for detecting
> the appropriate mode based on strap pins and ID pin.
>
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  drivers/usb/cdns3/Makefile |   2 +-
>  drivers/usb/cdns3/core.c   |  27 +++--
>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>  4 files changed, 372 insertions(+), 8 deletions(-)
>  create mode 100644 drivers/usb/cdns3/drd.c
>  create mode 100644 drivers/usb/cdns3/drd.h
>
> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> index 02d25b23c5d3..e779b2a2f8eb 100644
> --- a/drivers/usb/cdns3/Makefile
> +++ b/drivers/usb/cdns3/Makefile
> @@ -1,5 +1,5 @@
>  obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
>
> -cdns3-y                                        := core.o
> +cdns3-y                                        := core.o drd.o
>  cdns3-pci-y                            := cdns3-pci-wrap.o
> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> index f9055d4da67f..dbee4325da7f 100644
> --- a/drivers/usb/cdns3/core.c
> +++ b/drivers/usb/cdns3/core.c
> @@ -17,6 +17,7 @@
>
>  #include "gadget.h"
>  #include "core.h"
> +#include "drd.h"
>
>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>  {
> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>  {
>         if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> -               //TODO: implements selecting device/host mode
> -               return CDNS3_ROLE_HOST;
> +               if (cdns3_is_host(cdns))
> +                       return CDNS3_ROLE_HOST;
> +               if (cdns3_is_device(cdns))
> +                       return CDNS3_ROLE_GADGET;
>         }
>         return cdns->roles[CDNS3_ROLE_HOST]
>                 ? CDNS3_ROLE_HOST
> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>         struct cdns3 *cdns = data;
>         irqreturn_t ret = IRQ_NONE;
>
> +       if (cdns->dr_mode == USB_DR_MODE_OTG) {
> +               ret = cdns3_drd_irq(cdns);
> +               if (ret == IRQ_HANDLED)
> +                       return ret;
> +       }
> +
>         /* Handle device/host interrupt */
>         if (cdns->role != CDNS3_ROLE_END)
>                 ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
> @@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
>
>         cdns = container_of(work, struct cdns3, role_switch_wq);
>
> -       //TODO: implements this functions.
> -       //host = cdns3_is_host(cdns);
> -       //device = cdns3_is_device(cdns);
> -       host = 1;
> -       device = 0;
> +       host = cdns3_is_host(cdns);
> +       device = cdns3_is_device(cdns);
>
>         if (host)
>                 role = CDNS3_ROLE_HOST;
> @@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
>         pm_runtime_get_sync(cdns->dev);
>         cdns3_role_stop(cdns);
>
> +       if (cdns->desired_dr_mode != cdns->current_dr_mode) {
> +               cdns3_drd_update_mode(cdns);
> +               host = cdns3_is_host(cdns);
> +               device = cdns3_is_device(cdns);
> +       }
> +
>         if (host) {
>                 if (cdns->roles[CDNS3_ROLE_HOST])
>                         cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
> @@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
>         if (ret)
>                 goto err2;
>
> +       ret = cdns3_drd_init(cdns);
>         if (ret)
>                 goto err2;
>
> diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
> new file mode 100644
> index 000000000000..ac741c80e776
> --- /dev/null
> +++ b/drivers/usb/cdns3/drd.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence USBSS DRD Driver.
> + *
> + * Copyright (C) 2018 Cadence.
> + *
> + * Author: Pawel Laszczak <pawell@cadence.com
> + *
> + */
> +#include <linux/kernel.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/usb/otg.h>
> +
> +#include "gadget.h"
> +#include "drd.h"
> +
> +/**
> + * cdns3_set_mode - change mode of OTG Core
> + * @cdns: pointer to context structure
> + * @mode: selected mode from cdns_role
> + */
> +void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
> +{
> +       u32 reg;
> +
> +       cdns->current_dr_mode = mode;
> +       switch (mode) {
> +       case USB_DR_MODE_PERIPHERAL:
> +               dev_info(cdns->dev, "Set controller to Gadget mode\n");
> +               writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
> +                      &cdns->otg_regs->cmd);
> +               break;
> +       case USB_DR_MODE_HOST:
> +               dev_info(cdns->dev, "Set controller to Host mode\n");
> +               writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
> +                      &cdns->otg_regs->cmd);
> +               break;
> +       case USB_DR_MODE_OTG:
> +               dev_info(cdns->dev, "Set controller to OTG mode\n");
> +               reg = readl(&cdns->otg_regs->ctrl1);
> +               reg |= OTGCTRL1_IDPULLUP;
> +               writel(reg, &cdns->otg_regs->ctrl1);
> +
> +               /* wait until valid ID (ID_VALUE) can be sampled (50ms). */
> +               mdelay(50);

Usually, each big delay needs well documentation, eg, from hardware's
documentation.
And use sleep delay, eg, msleep or usleep_range.

> +               break;
> +       default:
> +               cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
> +               dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
> +               return;
> +       }
> +}
> +
> +static int cdns3_otg_get_id(struct cdns3 *cdns)
> +{
> +       int id;
> +
> +       id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
> +       dev_dbg(cdns->dev, "OTG ID: %d", id);
> +       return id;
> +}
> +
> +int cdns3_is_host(struct cdns3 *cdns)
> +{
> +       if (cdns->current_dr_mode == USB_DR_MODE_HOST)
> +               return 1;
> +       else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
> +               if (!cdns3_otg_get_id(cdns))
> +                       return 1;
> +
> +       return 0;
> +}
> +
> +int cdns3_is_device(struct cdns3 *cdns)
> +{
> +       if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
> +               return 1;
> +       else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
> +               if (cdns3_otg_get_id(cdns))
> +                       return 1;
> +
> +       return 0;
> +}
> +

You could move above into cdns_get_role.

> +/**
> + * cdns3_otg_disable_irq - Disable all OTG interrupts
> + * @cdns: Pointer to controller context structure
> + */
> +static void cdns3_otg_disable_irq(struct cdns3 *cdns)
> +{
> +       writel(0, &cdns->otg_regs->ien);
> +}
> +
> +/**
> + * cdns3_otg_enable_irq - enable id and sess_valid interrupts
> + * @cdns: Pointer to controller context structure
> + */
> +static void cdns3_otg_enable_irq(struct cdns3 *cdns)
> +{
> +       writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
> +              OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
> +}
> +
> +/**
> + * cdns3_init_otg_mode - initialize drd controller
> + * @cdns: Pointer to controller context structure
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +static void cdns3_init_otg_mode(struct cdns3 *cdns)
> +{
> +       cdns3_otg_disable_irq(cdns);
> +       /* clear all interrupts */
> +       writel(~0, &cdns->otg_regs->ivect);
> +
> +       cdns3_set_mode(cdns, USB_DR_MODE_OTG);
> +
> +       cdns3_otg_enable_irq(cdns);
> +}
> +
> +/**
> + * cdns3_drd_update_mode - initialize mode of operation
> + * @cdns: Pointer to controller context structure
> + *
> + * Returns 0 on success otherwise negative errno
> + */
> +int cdns3_drd_update_mode(struct cdns3 *cdns)
> +{
> +       int ret = 0;
> +
> +       switch (cdns->desired_dr_mode) {
> +       case USB_DR_MODE_PERIPHERAL:
> +               cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
> +               break;
> +       case USB_DR_MODE_HOST:
> +               cdns3_set_mode(cdns, USB_DR_MODE_HOST);
> +               break;
> +       case USB_DR_MODE_OTG:
> +               cdns3_init_otg_mode(cdns);
> +               break;
> +       default:
> +               dev_err(cdns->dev, "Unsupported mode of operation %d\n",
> +                       cdns->dr_mode);
> +               return -EINVAL;
> +       }
> +
> +       return ret;
> +}
> +
> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns)
> +{
> +       irqreturn_t ret = IRQ_NONE;
> +       u32 reg;
> +
> +       if (cdns->dr_mode != USB_DR_MODE_OTG)
> +               return ret;
> +
> +       reg = readl(&cdns->otg_regs->ivect);
> +       if (!reg)
> +               return ret;
> +
> +       if (reg & OTGIEN_ID_CHANGE_INT) {
> +               int id = cdns3_otg_get_id(cdns);
> +
> +               dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
> +                       cdns3_otg_get_id(cdns));
> +
> +               if (id)
> +                       cdns->role = CDNS3_ROLE_GADGET;
> +               else
> +                       cdns->role = CDNS3_ROLE_HOST;
> +
> +               queue_work(system_freezable_wq, &cdns->role_switch_wq);
> +
> +               ret = IRQ_HANDLED;
> +       }
> +
> +       writel(~0, &cdns->otg_regs->ivect);
> +       return IRQ_HANDLED;
> +}
> +
> +int cdns3_drd_init(struct cdns3 *cdns)
> +{
> +       enum usb_dr_mode dr_mode;
> +       int ret = 0;
> +       u32 state;
> +
> +       state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts));
> +
> +       dr_mode = cdns->dr_mode;
> +       if (state == OTGSTS_STRAP_HOST) {
> +               dev_info(cdns->dev, "Controller strapped to HOST\n");
> +               dr_mode = USB_DR_MODE_HOST;
> +               if (cdns->dr_mode != USB_DR_MODE_HOST &&
> +                   cdns->dr_mode != USB_DR_MODE_OTG)
> +                       ret = -EINVAL;
> +       } else if (state == OTGSTS_STRAP_GADGET) {
> +               dev_info(cdns->dev, "Controller strapped to PERIPHERAL\n");
> +               dr_mode = USB_DR_MODE_PERIPHERAL;
> +               if (cdns->dr_mode != USB_DR_MODE_PERIPHERAL &&
> +                   cdns->dr_mode != USB_DR_MODE_OTG)
> +                       ret = -EINVAL;
> +       }
> +
> +       if (ret) {
> +               dev_err(cdns->dev, "Incorrect DRD configuration\n");
> +               return ret;
> +       }
> +
> +       //Updating DR mode according to strap.
> +       cdns->dr_mode = dr_mode;
> +       cdns->desired_dr_mode = dr_mode;
> +       cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
> +
> +       dev_info(cdns->dev, "Controller Device ID: %08lx, Revision ID: %08lx\n",
> +                CDNS_RID(readl(&cdns->otg_regs->rid)),
> +                CDNS_DID(readl(&cdns->otg_regs->did)));
> +
> +       state = readl(&cdns->otg_regs->sts);
> +       if (OTGSTS_OTG_NRDY(state) != 0) {
> +               dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
> +               return -ENODEV;
> +       }
> +
> +       ret = cdns3_drd_update_mode(cdns);
> +
> +       return ret;
> +}
> diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
> new file mode 100644
> index 000000000000..0faa7520ecac
> --- /dev/null
> +++ b/drivers/usb/cdns3/drd.h
> @@ -0,0 +1,122 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence USB3 DRD part of USBSS driver

Header file

Peter

> + *
> + * Copyright (C) 2018 Cadence.
> + *
> + * Author: Pawel Laszczak <pawell@cadence.com>
> + */
> +#ifndef __LINUX_CDNS3_DRD
> +#define __LINUX_CDNS3_DRD
> +
> +#include <linux/usb/otg.h>
> +#include <linux/phy/phy.h>
> +#include "core.h"
> +
> +/*  DRD register interface. */
> +struct cdns3_otg_regs {
> +       __le32 did;
> +       __le32 rid;
> +       __le32 capabilities;
> +       __le32 reserved1;
> +       __le32 cmd;
> +       __le32 sts;
> +       __le32 state;
> +       __le32 reserved2;
> +       __le32 ien;
> +       __le32 ivect;
> +       __le32 refclk;
> +       __le32 tmr;
> +       __le32 reserved3[4];
> +       __le32 simulate;
> +       __le32 override;
> +       __le32 susp_ctrl;
> +       __le32 reserved4;
> +       __le32 anasts;
> +       __le32 adp_ramp_time;
> +       __le32 ctrl1;
> +       __le32 ctrl2;
> +};
> +
> +/* CDNS_RID - bitmasks */
> +#define CDNS_RID(p)                    ((p) & GENMASK(15, 0))
> +
> +/* CDNS_VID - bitmasks */
> +#define CDNS_DID(p)                    ((p) & GENMASK(31, 0))
> +
> +/* OTGCMD - bitmasks */
> +/* "Request the bus for Device mode. */
> +#define OTGCMD_DEV_BUS_REQ     BIT(0)
> +/* Request the bus for Host mode */
> +#define OTGCMD_HOST_BUS_REQ            BIT(1)
> +/* Enable OTG mode. */
> +#define OTGCMD_OTG_EN                  BIT(2)
> +/* Disable OTG mode */
> +#define OTGCMD_OTG_DIS                 BIT(3)
> +/*"Configure OTG as A-Device. */
> +#define OTGCMD_A_DEV_EN                        BIT(4)
> +/*"Configure OTG as A-Device. */
> +#define OTGCMD_A_DEV_DIS               BIT(5)
> +/* Drop the bus for Device mod e. */
> +#define OTGCMD_DEV_BUS_DROP            BIT(8)
> +/* Drop the bus for Host mode*/
> +#define OTGCMD_HOST_BUS_DROP           BIT(9)
> +/* Power Down USBSS-DEV. */
> +#define OTGCMD_DEV_POWER_OFF           BIT(11)
> +/* Power Down CDNSXHCI. */
> +#define OTGCMD_HOST_POWER_OFF          BIT(12)
> +
> +/* OTGIEN - bitmasks */
> +/* ID change interrupt enable */
> +#define OTGIEN_ID_CHANGE_INT           BIT(0)
> +/* Vbusvalid fall detected interrupt enable.*/
> +#define OTGIEN_VBUSVALID_RISE_INT      BIT(4)
> +/* Vbusvalid fall detected interrupt enable */
> +#define OTGIEN_VBUSVALID_FALL_INT      BIT(5)
> +
> +/* OTGSTS - bitmasks */
> +/*
> + * Current value of the ID pin. It is only valid when idpullup in
> + *  OTGCTRL1_TYPE register is set to '1'.
> + */
> +#define OTGSTS_ID_VALUE                        BIT(0)
> +/* Current value of the vbus_valid */
> +#define OTGSTS_VBUS_VALID              BIT(1)
> +/* Current value of the b_sess_vld */
> +#define OTGSTS_SESSION_VALID           BIT(2)
> +/*Device mode is active*/
> +#define OTGSTS_DEV_ACTIVE              BIT(3)
> +/* Host mode is active. */
> +#define OTGSTS_HOST_ACTIVE             BIT(4)
> +/* OTG Controller not ready. */
> +#define OTGSTS_OTG_NRDY_MASK           BIT(11)
> +#define OTGSTS_OTG_NRDY(p)             ((p) & OTGSTS_OTG_NRDY_MASK)
> +/*
> + * Value of the strap pins.
> + * 000 - no default configuration
> + * 010 - Controller initiall configured as Host
> + * 100 - Controller initially configured as Device
> + */
> +#define OTGSTS_STRAP(p)                        (((p) & GENMASK(14, 12)) >> 12)
> +#define OTGSTS_STRAP_NO_DEFAULT_CFG    0x00
> +#define OTGSTS_STRAP_HOST_OTG          0x01
> +#define OTGSTS_STRAP_HOST              0x02
> +#define OTGSTS_STRAP_GADGET            0x04
> +/* Host mode is turned on. */
> +#define OTGSTSE_XHCI_READYF            BIT(26)
> +/* "Device mode is turned on .*/
> +#define OTGSTS_DEV_READY               BIT(27)
> +
> +/* OTGREFCLK - bitmasks */
> +#define OTGREFCLK_STB_CLK_SWITCH_EN    BIT(31)
> +
> +/* OTGCTRL1 - bitmasks */
> +#define OTGCTRL1_IDPULLUP              BIT(24)
> +
> +int cdns3_is_host(struct cdns3 *cdns);
> +int cdns3_is_device(struct cdns3 *cdns);
> +int cdns3_drd_init(struct cdns3 *cdns);
> +int cdns3_drd_update_mode(struct cdns3 *cdns);
> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns);
> +
> +#endif /* __LINUX_CDNS3_DRD */
> --
> 2.17.1
>

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04  8:50     ` Peter Chen
@ 2018-12-04 10:46       ` Roger Quadros
  2018-12-05  8:57         ` Peter Chen
  2018-12-06  9:31         ` Pawel Laszczak
  2018-12-05 19:24       ` Pawel Laszczak
  2018-12-05 19:42       ` Pawel Laszczak
  2 siblings, 2 replies; 85+ messages in thread
From: Roger Quadros @ 2018-12-04 10:46 UTC (permalink / raw)
  To: Peter Chen
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul



On 04/12/18 10:50, Peter Chen wrote:
>>> + * Cadence USBSS DRD Driver.
>>> + *
>>> + * Copyright (C) 2018 Cadence.
>>> + *
>>> + * Author: Peter Chen <peter.chen@nxp.com>
>>> + *         Pawel Laszczak <pawell@cadence.com>
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/io.h>
>>> +#include <linux/pm_runtime.h>
>>> +
>>> +#include "gadget.h"
>>> +#include "core.h"
>>> +
>>> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>> +{
>>> +     WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
>>> +     return cdns->roles[cdns->role];
>>> +}
>>> +
>>> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
>>> +{
>>> +     int ret;
>>> +
>>> +     if (role >= CDNS3_ROLE_END)
>>
>> WARN_ON()?
>>
>>> +             return 0;
>>> +
>>> +     if (!cdns->roles[role])
>>> +             return -ENXIO;
>>> +
>>> +     mutex_lock(&cdns->mutex);
>>> +     cdns->role = role;
>>> +     ret = cdns->roles[role]->start(cdns);
>>> +     mutex_unlock(&cdns->mutex);
>>> +     return ret;
>>> +}
>>> +
>>> +static inline void cdns3_role_stop(struct cdns3 *cdns)
>>> +{
>>> +     enum cdns3_roles role = cdns->role;
>>> +
>>> +     if (role == CDNS3_ROLE_END)
>>
>> WARN_ON(role >= CNDS3_ROLE_END) ?
>>
>>> +             return;
>>> +
>>> +     mutex_lock(&cdns->mutex);
>>> +     cdns->roles[role]->stop(cdns);
>>> +     cdns->role = CDNS3_ROLE_END;
>>
>> Why change the role here? You are just stopping the role not changing it.
>> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>> if required without error.
>>
> 
> The current version of this IP has some issues to detect vbus status correctly,
> we have to force vbus status accordingly, so we need a status to indicate
> vbus disconnection, and add some code to let controller know vbus
> removal, in that case, the controller's state machine can be correct.
> So, we increase one role 'CDNS3_ROLE_END' to for this purpose.
> 
> CDNS3_ROLE_GADGET: gadget mode and VBUS on
> CDNS3_ROLE_HOST: host mode and VBUS on
> CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.
> 
> So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
> and need to set role as CDNS3_ROLE_END at ->stop for further handling at
> role switch routine.

OK. but still this (changing to ROLE_END) must be moved to the role switch routine
and the explanation you just mentioned must be added there.

> 
>>> +     mutex_unlock(&cdns->mutex);
>>> +}
>>> +
>>> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>> +{
>>> +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>> +             //TODO: implements selecting device/host mode
>>> +             return CDNS3_ROLE_HOST;
>>> +     }
>>> +     return cdns->roles[CDNS3_ROLE_HOST]
>>> +             ? CDNS3_ROLE_HOST
>>> +             : CDNS3_ROLE_GADGET;
>>
>> Why not just
>>         return cdns->role;
>>
>> I'm wondering if we really need this function.
> 
> cdns->role gets from cdns3_get_role, and this API tells role at the runtime.
> If both roles are supported, the role is decided by external
> conditions, eg, vbus/id
> or external connector. If only single role is supported, only one role structure
> is allocated, cdns->roles[CDNS3_ROLE_HOST] or cdns->roles[CDNS3_ROLE_GADGET]
> 

How about adding this description in function documentation.

>>> +}
>>
>>> +
>>> +/**
>>> + * cdns3_core_init_role - initialize role of operation
>>> + * @cdns: Pointer to cdns3 structure
>>> + *
>>> + * Returns 0 on success otherwise negative errno
>>> + */
>>> +static int cdns3_core_init_role(struct cdns3 *cdns)
>>> +{
>>> +     struct device *dev = cdns->dev;
>>> +     enum usb_dr_mode dr_mode;
>>> +
>>> +     dr_mode = usb_get_dr_mode(dev);
>>> +     cdns->role = CDNS3_ROLE_END;
>>> +
>>> +     /*
>>> +      * If driver can't read mode by means of usb_get_dr_mdoe function then
>>> +      * chooses mode according with Kernel configuration. This setting
>>> +      * can be restricted later depending on strap pin configuration.
>>> +      */
>>> +     if (dr_mode == USB_DR_MODE_UNKNOWN) {
>>> +             if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
>>> +                 IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>>> +                     dr_mode = USB_DR_MODE_OTG;
>>> +             else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
>>> +                     dr_mode = USB_DR_MODE_HOST;
>>> +             else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>>> +                     dr_mode = USB_DR_MODE_PERIPHERAL;
>>> +     }
>>> +
>>> +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>>> +             //TODO: implements host initialization
>>
>>                 /* TODO: Add host role */ ?
>>
>>> +     }
>>> +
>>> +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>>> +             //TODO: implements device initialization
>>
>>                 /* TODO: Add device role */ ?
>>
> 
> Yes, it needs to allocate cdns->roles[CDNS3_ROLE_HOST] and
> cdns->roles[CDNS3_ROLE_GADGET].
> 
>>> +     }
>>> +
>>> +     if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>>> +             dev_err(dev, "no supported roles\n");
>>> +             return -ENODEV;
>>> +     }
>>> +
>>> +     cdns->dr_mode = dr_mode;
> 
> Pawel, why dr_mode needs to be introduced?
> 
>>> +     return 0;
>>> +}
>>> +
>>> +/**
>>> + * cdns3_irq - interrupt handler for cdns3 core device
>>> + *
>>> + * @irq: irq number for cdns3 core device
>>> + * @data: structure of cdns3
>>> + *
>>> + * Returns IRQ_HANDLED or IRQ_NONE
>>> + */
>>> +static irqreturn_t cdns3_irq(int irq, void *data)
>>> +{
>>> +     struct cdns3 *cdns = data;
>>> +     irqreturn_t ret = IRQ_NONE;
>>> +
>>> +     /* Handle device/host interrupt */
>>> +     if (cdns->role != CDNS3_ROLE_END)
>>
>> Is it because of this that you need to set role to END at role_stop?
>> I think it is better to add a state variable to struct cdns3_role_driver, so we can
>> check if it is active or stopped.
>>
>> e.g.
>>         if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)
>>
>>> +             ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>>> +
>>> +     return ret;
>>> +}
>>> +
> 
>  CDNS3_ROLE_END is introduced from above comments, we don't
> need another flag for it.
> If cdns->role == CDNS3_ROLE_END, it handles VBUS and ID interrupt.
> 
>>> +static void cdns3_remove_roles(struct cdns3 *cdns)
>>
>> Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?
>>
> 
> It is planed to called when at ->remove.
>>> +{
>>> +     //TODO: implements this function
>>> +}
>>
>>> +
>>> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>>> +{
>>> +     enum cdns3_roles current_role;
>>> +     int ret = 0;
>>> +
>>> +     current_role = cdns->role;
>>> +
>>> +     if (role == CDNS3_ROLE_END)
>>> +             return 0;
>>
>> role == END looks like error state. and it should never happen.
>> WARN here?
>>
> 
> See my comments above.
> 
>>> +
>>> +     dev_dbg(cdns->dev, "Switching role");
>>> +
>>
>> Don't you have to stop the previous role before starting the new role?
>>
> 
> Yes, it is needed. Pawel may simply some flows to suit his platform.
> 
>>> +     ret = cdns3_role_start(cdns, role);
>>> +     if (ret) {
>>> +             /* Back to current role */
>>> +             dev_err(cdns->dev, "set %d has failed, back to %d\n",
>>> +                     role, current_role);
>>> +             ret = cdns3_role_start(cdns, current_role);
>>> +     }
>>> +
>>> +     return ret;
>>> +}
>>> +
>>> +/**
>>> + * cdns3_role_switch - work queue handler for role switch
>>> + *
>>> + * @work: work queue item structure
>>> + *
>>> + * Handles below events:
>>> + * - Role switch for dual-role devices
>>> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
>>> + */
>>> +static void cdns3_role_switch(struct work_struct *work)
>>> +{
>>> +     enum cdns3_roles role = CDNS3_ROLE_END;
>>> +     struct cdns3 *cdns;
>>> +     bool device, host;
>>> +
>>> +     cdns = container_of(work, struct cdns3, role_switch_wq);
>>> +
>>> +     //TODO: implements this functions.
>>> +     //host = cdns3_is_host(cdns);
>>> +     //device = cdns3_is_device(cdns);
>>> +     host = 1;
>>> +     device = 0;
>>> +
>>> +     if (host)
>>> +             role = CDNS3_ROLE_HOST;
>>> +     else if (device)
>>> +             role = CDNS3_ROLE_GADGET;
>>> +
>>> +     if (cdns->desired_dr_mode == cdns->current_dr_mode &&
>>> +         cdns->role == role)
>>> +             return;
>>> +
>>
>> I think all the below code can be moved to cdns3_do_role_switch().
>>
>>> +     pm_runtime_get_sync(cdns->dev);
>>> +     cdns3_role_stop(cdns);
>>> +
>>> +     if (host) {
>>> +             if (cdns->roles[CDNS3_ROLE_HOST])
>>> +                     cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>>> +             pm_runtime_put_sync(cdns->dev);
>>> +             return;
>>> +     }
>>> +
>>> +     if (device)
>>> +             cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
>>> +     else
>>> +             cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
>>> +
>>> +     pm_runtime_put_sync(cdns->dev);
>>> +}
>>> +
>>> +/**
>>> + * cdns3_probe - probe for cdns3 core device
>>> + * @pdev: Pointer to cdns3 core platform device
>>> + *
>>> + * Returns 0 on success otherwise negative errno
>>> + */
>>> +static int cdns3_probe(struct platform_device *pdev)
>>> +{
>>> +     struct device *dev = &pdev->dev;
>>> +     struct resource *res;
>>> +     struct cdns3 *cdns;
>>> +     void __iomem *regs;
>>> +     int ret;
>>> +
>>> +     cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
>>> +     if (!cdns)
>>> +             return -ENOMEM;
>>> +
>>> +     cdns->dev = dev;
>>> +
>>> +     platform_set_drvdata(pdev, cdns);
>>> +
>>> +     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
>>> +     if (!res) {
>>> +             dev_err(dev, "missing IRQ\n");
>>> +             return -ENODEV;
>>> +     }
>>> +     cdns->irq = res->start;
>>> +
>>> +     /*
>>> +      * Request memory region
>>> +      * region-0: xHCI
>>> +      * region-1: Peripheral
>>> +      * region-2: OTG registers
>>> +      */
>>
>> The memory region order is different from the dt-binding.
>> There it is OTG, host(xhci), device (peripheral).
>>
>>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> +     regs = devm_ioremap_resource(dev, res);
>>> +
>>> +     if (IS_ERR(regs))
>>> +             return PTR_ERR(regs);
>>> +     cdns->xhci_regs = regs;
>>> +     cdns->xhci_res = res;
>>> +
>>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>>> +     regs = devm_ioremap_resource(dev, res);
>>> +     if (IS_ERR(regs))
>>> +             return PTR_ERR(regs);
>>> +     cdns->dev_regs  = regs;
>>> +
>>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>>> +     regs = devm_ioremap_resource(dev, res);
>>> +     if (IS_ERR(regs))
>>> +             return PTR_ERR(regs);
>>> +     cdns->otg_regs = regs;
>>> +
>>> +     mutex_init(&cdns->mutex);
>>> +
>>> +     cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>>
>> "cdns3,usbphy" is not documented in dt-binding.
>>
>>> +     if (IS_ERR(cdns->phy)) {
>>> +             dev_info(dev, "no generic phy found\n");
>>> +             cdns->phy = NULL;
>>> +             /*
>>> +              * fall through here!
>>> +              * if no generic phy found, phy init
>>> +              * should be done under boot!
>>> +              */
>>
>> No you shouldn't fall through always if it is an error condition.
>> Something like this should work better.
>>
>>         if (IS_ERR(cnds->phy)) {
>>                 ret = PTR_ERR(cdns->phy);
>>                 if (ret == -ENOSYS || ret == -ENODEV) {
>>                         cdns->phy = NULL;
>>                 } else if (ret == -EPROBE_DEFER) {
>>                         return ret;
>>                 } else {
>>                         dev_err(dev, "no phy found\n");
>>                         goto err0;
>>                 }
>>         }
>>
>> So if PHY was provided in DT, and PHY support/drivers is present
>> and error condition means something is wrong and we have to error out.
>>
>>> +     } else {
>>> +             phy_init(cdns->phy);
>>> +     }
>>
>> You can do phy_init() outside the else.
>>
>>> +
>>> +     ret = cdns3_core_init_role(cdns);
>>> +     if (ret)
>>> +             goto err1;
>>> +
>>> +     INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
>>> +     if (ret)
>>> +             goto err2;
>>> +
>>> +     if (ret)
>>> +             goto err2;
>>> +
>>> +     cdns->role = cdns3_get_role(cdns);
>>
>> I think this should move to cdns3_core_init_role().
>>
> 
> I agree.
> 
>>> +
>>> +     ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
>>> +                            dev_name(dev), cdns);
>>> +
>>> +     if (ret)
>>> +             goto err2;
>>
>> How about moving request_irq to before cdsn3_core_init_role()?
>>
>> Then you can move cdns3_role_start() as well to core_init_role().
>>
> 
> Usually, we request irq after hardware initialization has finished, if not,
> there may unexpected interrupt.

Doesn't kernel warn if interrupt happens and there is no handler?
To avoid that I was suggesting to request_irq first.

cheers,
-roger

-- 
Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller.
  2018-11-18 10:08 ` [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller Pawel Laszczak
  2018-11-23 10:53   ` Roger Quadros
@ 2018-12-04 22:41   ` Rob Herring
  2018-12-06 10:26     ` Pawel Laszczak
  1 sibling, 1 reply; 85+ messages in thread
From: Rob Herring @ 2018-12-04 22:41 UTC (permalink / raw)
  To: Pawel Laszczak
  Cc: devicetree, gregkh, linux-usb, rogerq, linux-kernel, adouglas,
	jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez, kurahul

On Sun, Nov 18, 2018 at 10:08:59AM +0000, Pawel Laszczak wrote:
> Thsi patch aim at documenting USB related dt-bindings for the

typo

> Cadence USBSS-DRD controller.
> 
> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> ---
>  .../devicetree/bindings/usb/cdns3-usb.txt       | 17 +++++++++++++++++
>  1 file changed, 17 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/usb/cdns3-usb.txt
> 
> diff --git a/Documentation/devicetree/bindings/usb/cdns3-usb.txt b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
> new file mode 100644
> index 000000000000..f9e953f32d47
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
> @@ -0,0 +1,17 @@
> +Binding for the Cadence USBSS-DRD controller
> +
> +Required properties:
> +  - reg: Physical base address and size of the controller's register area.

1 or 3 regions? If 3, what is each one and the order?

> +  - compatible: Should contain: "cdns,usb3"

Only one version and one set of bugs?

> +  - interrupts: Interrupt specifier. Refer to interrupt bindings.

How many interrupts?

> +
> +
> +Example:
> +	cdns3@f3000000 {

usb@...

> +		compatible = "cdns,usb3";
> +		interrupts = <USB_IRQ  7 IRQ_TYPE_LEVEL_HIGH>;
> +		reg = <0xf3000000 0x10000	//memory area for OTG/DRD registers
> +			0xf3010000 0x10000	//memory area for HOST registers
> +			0xf3020000 0x10000>;	//memory area for Device registers
> +	};
> +
> -- 
> 2.17.1
> 

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04  7:11       ` Peter Chen
@ 2018-12-05  7:19         ` Pawel Laszczak
  2018-12-05  8:55           ` Alan Douglas
  0 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-05  7:19 UTC (permalink / raw)
  To: Peter Chen
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	Alan Douglas, jbergsagel, nsekhar, nm, Suresh Punnoose,
	peter.chen, Pawel Jez, Rahul Kumar

Hi,

>> >>
>> >> Patch adds core.c and core.h file that implements initialization
>> >> of platform driver and adds function responsible for selecting,
>> >> switching and running appropriate Device/Host mode.
>> >>
>> >> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> >> ---
>> >>  drivers/usb/cdns3/Makefile |   2 +
>> >>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
>> >>  drivers/usb/cdns3/core.h   | 100 +++++++++
>> >>  3 files changed, 515 insertions(+)
>> >>  create mode 100644 drivers/usb/cdns3/core.c
>> >>  create mode 100644 drivers/usb/cdns3/core.h
>> >>
>> >> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> >> index dcdd62003c6a..02d25b23c5d3 100644
>> >> --- a/drivers/usb/cdns3/Makefile
>> >> +++ b/drivers/usb/cdns3/Makefile
>> >> @@ -1,3 +1,5 @@
>> >> +obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
>> >>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
>> >>
>> >> +cdns3-y                                        := core.o
>> >>  cdns3-pci-y                            := cdns3-pci-wrap.o
>> >> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> >> new file mode 100644
>> >> index 000000000000..f9055d4da67f
>> >> --- /dev/null
>> >> +++ b/drivers/usb/cdns3/core.c
>> >> @@ -0,0 +1,413 @@
>> >> +// SPDX-License-Identifier: GPL-2.0
>> >> +/*
>> >> + * Cadence USBSS DRD Driver.
>> >> + *
>> >> + * Copyright (C) 2018 Cadence.
>> >> + *
>> >
>> >Please add NXP copyright too.
>>
>> Ok, I don't know why I omitted this.
>> I know that you are the main author of this file
>> Sorry for that.
>>
>> One additional question. What year I should add in Copyright for NXP?.
>> The original year 2017 or I should modified all to 2018.
>>
>Please use below copyright, thanks.
>
>Copyright 2017-2018 NXP

I add this in all files  modified or created by you.
>
>
>
>> >> +       mutex_init(&cdns->mutex);
>> >> +
>> >> +       cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>> >> +       if (IS_ERR(cdns->phy)) {
>> >> +               dev_info(dev, "no generic phy found\n");
>> >> +               cdns->phy = NULL;
>> >> +               /*
>> >> +                * fall through here!
>> >> +                * if no generic phy found, phy init
>> >> +                * should be done under boot!
>> >> +                */
>> >
>> >If the phy driver is defer-probed, it will be here, it is not an error.
>> >I think you could have a generic phy driver or usb generic phy driver
>> >(drivers/usb/phy/phy-generic.c) even you don't need any operations for
>> >PHY. It will be easy for other platforms.
>>
>> Yes, Roger ask me to modify this fragment. In next version it will look like:
>>         cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>>         if (IS_ERR(cdns->phy)) {
>>                 ret = PTR_ERR(cdns->phy);
>>                 if (ret == -ENOSYS || ret == -ENODEV) {
>>                         cdns->phy = NULL;
>>                 } else if (ret == -EPROBE_DEFER) {
>>                         return ret;
>>                 } else {
>>                         dev_err(dev, "no phy found\n");
>>                         goto err0;
>>                 }
>>         }
>>
>>         phy_init(cdns->phy);
>>
>> We are going to use phy driver. I don't know if it correct.
>> I don't have experience in this filed.
>> We need phy initialization but I don't have testing platform now.
>> In most usb drivers I see that there are used usb phy driverd instead phy dirverd.
>>
>
>At my CDNS3 platform, there are some USB PHY initialization for register setting
>and clock enable. You could add generic PHY driver under: drivers/phy/cadence/.
>
>Above PHY initialization code is OK for me.

It will be added as separate driver. 
I think that Allan Douglas working on it. 
I ask him to add you to -cc in patch for phy.

>
>
>> >> +static void __exit cdns3_driver_platform_unregister(void)
>> >> +{
>> >> +       platform_driver_unregister(&cdns3_driver);
>> >> +}
>> >> +module_exit(cdns3_driver_platform_unregister);
>> >> +
>> >> +MODULE_ALIAS("platform:cdns3");
>> >> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
>> >> +MODULE_LICENSE("GPL v2");
>> >> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
>> >> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
>> >> new file mode 100644
>> >> index 000000000000..7c8204fe4d3d
>> >> --- /dev/null
>> >> +++ b/drivers/usb/cdns3/core.h
>> >> @@ -0,0 +1,100 @@
>> >> +/* SPDX-License-Identifier: GPL-2.0 */
>> >> +/*
>> >> + * Cadence USBSS DRD Driver.
>> >> + *
>> >
>> >Header file
>> I don't understand. What is wrong ?
>> >
>
>The comment for this file
>
>Cadence USBSS DRD Core Header File

Ok, thanks 
Cheers, 
Pawel

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

* Re: [RFC PATCH v2 06/15] usb:cdns3: Adds Host support
  2018-11-23 14:23   ` Roger Quadros
  2018-11-26  8:24     ` Pawel Laszczak
@ 2018-12-05  8:41     ` Peter Chen
  1 sibling, 0 replies; 85+ messages in thread
From: Peter Chen @ 2018-12-05  8:41 UTC (permalink / raw)
  To: rogerq
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

> > --- a/drivers/usb/cdns3/Makefile
> > +++ b/drivers/usb/cdns3/Makefile
> > @@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3)                       += cdns3.o
> >  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)     += cdns3-pci.o
> >
> >  cdns3-y                                      := core.o drd.o
> > +cdns3-$(CONFIG_USB_CDNS3_HOST)          += host.o
> >  cdns3-pci-y                          := cdns3-pci-wrap.o
> > diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> > index dbee4325da7f..4cb820be9ff3 100644
> > --- a/drivers/usb/cdns3/core.c
> > +++ b/drivers/usb/cdns3/core.c
> > @@ -17,6 +17,7 @@
> >
> >  #include "gadget.h"
> >  #include "core.h"
> > +#include "host-export.h"
> >  #include "drd.h"
> >
> >  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> > @@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
> >       }
> >
> >       if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
> > -             //TODO: implements host initialization
> > +             if (cdns3_host_init(cdns))
> > +                     dev_info(dev, "doesn't support host\n");
>
> dev_err()
>
> And you need to error out with error code.

There are two cases for it:
- There is no cdsn3_host_init implementation due to kernel configuration,
- The error happens at cdsn3_host_init

We may need to handle these two different cases.


> > +     struct usb_hcd  *hcd;
> > +
> > +     if (dev)
> > +             hcd = dev_get_drvdata(dev);
> > +     else
> > +             return IRQ_NONE;
> > +
> > +     if (hcd)
> > +             return usb_hcd_irq(cdns->irq, hcd);
> > +     else
> > +             return IRQ_NONE;
>
> Why can't you just reuse the xhci-platform driver and let it manage the IRQ?
> Since it is a shared IRQ, different drivers can request the same IRQ and return IRQ_NONE
> if the IRQ wasn't from their device.
>

When I began to write this code, there are not so many quirks can be
used at xhci-plat.c,
it seems we can re-use this common code at cdns3 driver now, I will
try to integrate it.
Thanks for your suggestion.

Peter

> > +}
> > +
> > +static void cdns3_host_release(struct device *dev)
> > +{
> > +     struct cdns3_host *host = container_of(dev, struct cdns3_host, dev);
> > +
> > +     kfree(host);
> > +}
> > +
> > +static int cdns3_host_start(struct cdns3 *cdns)
> > +{
> > +     struct cdns3_host *host;
> > +     struct device *dev;
> > +     struct device *sysdev;
> > +     struct xhci_hcd *xhci;
> > +     int ret;
> > +
> > +     host = kzalloc(sizeof(*host), GFP_KERNEL);
> > +     if (!host)
> > +             return -ENOMEM;
> > +
> > +     dev = &host->dev;
> > +     dev->release = cdns3_host_release;
> > +     dev->parent = cdns->dev;
> > +     dev_set_name(dev, "xhci-cdns3");
> > +     cdns->host_dev = dev;
> > +     ret = device_register(dev);
> > +     if (ret)
> > +             goto err1;
> > +
> > +     sysdev = cdns->dev;
> > +     /* Try to set 64-bit DMA first */
> > +     if (WARN_ON(!sysdev->dma_mask))
> > +             /* Platform did not initialize dma_mask */
> > +             ret = dma_coerce_mask_and_coherent(sysdev,
> > +                                                DMA_BIT_MASK(64));
> > +     else
> > +             ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
> > +
> > +     /* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
> > +     if (ret) {
> > +             ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
> > +             if (ret)
> > +                     return ret;
> > +     }
> > +
> > +     pm_runtime_set_active(dev);
> > +     pm_runtime_no_callbacks(dev);
> > +     pm_runtime_enable(dev);
> > +     host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
> > +                                  dev_name(dev), NULL);
> > +     if (!host->hcd) {
> > +             ret = -ENOMEM;
> > +             goto err2;
> > +     }
> > +
> > +     host->hcd->regs = cdns->xhci_regs;
> > +     host->hcd->rsrc_start = cdns->xhci_res->start;
> > +     host->hcd->rsrc_len = resource_size(cdns->xhci_res);
> > +
> > +     device_wakeup_enable(host->hcd->self.controller);
> > +     xhci = hcd_to_xhci(host->hcd);
> > +
> > +     xhci->main_hcd = host->hcd;
> > +     xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev,
> > +                                         dev_name(dev), host->hcd);
> > +     if (!xhci->shared_hcd) {
> > +             ret = -ENOMEM;
> > +             goto err3;
> > +     }
> > +
> > +     host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node);
> > +     xhci->shared_hcd->tpl_support = host->hcd->tpl_support;
> > +     ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED);
> > +     if (ret)
> > +             goto err4;
> > +
> > +     ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED);
> > +     if (ret)
> > +             goto err5;
> > +
> > +     device_set_wakeup_capable(dev, true);
>
> All this is being done by the xhci-plat.c
>
> You can make use of it by just creating a xhci-hcd platform device.
>
> e.g.
> platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
> platform_device_add_resources() to add IRQ and memory resource.
> platform_device_add_properties() to add any quirks.
> platform_device_add()
>
>
> > +
> > +     return 0;
> > +
> > +err5:
> > +     usb_remove_hcd(host->hcd);
> > +err4:
> > +     usb_put_hcd(xhci->shared_hcd);
> > +err3:
> > +     usb_put_hcd(host->hcd);
> > +err2:
> > +     device_del(dev);
> > +err1:
> > +     put_device(dev);
> > +     cdns->host_dev = NULL;
> > +     return ret;
> > +}
> > +
> > +static void cdns3_host_stop(struct cdns3 *cdns)
> > +{
> > +     struct device *dev = cdns->host_dev;
> > +     struct xhci_hcd *xhci;
> > +     struct usb_hcd  *hcd;
> > +
> > +     if (dev) {
> > +             hcd = dev_get_drvdata(dev);
> > +             xhci = hcd_to_xhci(hcd);
> > +             usb_remove_hcd(xhci->shared_hcd);
> > +             usb_remove_hcd(hcd);
> > +             synchronize_irq(cdns->irq);
> > +             usb_put_hcd(xhci->shared_hcd);
> > +             usb_put_hcd(hcd);
> > +             cdns->host_dev = NULL;
> > +             pm_runtime_set_suspended(dev);
> > +             pm_runtime_disable(dev);
> > +             device_del(dev);
> > +             put_device(dev);
> > +     }
>
> You can replace this with just
>         platform_device_unregister(xhci_dev);
>
> > +}
> > +
> > +#if CONFIG_PM
> > +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup)
> > +{
> > +     struct device *dev = cdns->host_dev;
> > +     struct xhci_hcd *xhci;
> > +
> > +     if (!dev)
> > +             return 0;
> > +
> > +     xhci = hcd_to_xhci(dev_get_drvdata(dev));
> > +     return xhci_suspend(xhci, do_wakeup);
> > +}
> > +
> > +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated)
> > +{
> > +     struct device *dev = cdns->host_dev;
> > +     struct xhci_hcd *xhci;
> > +
> > +     if (!dev)
> > +             return 0;
> > +
> > +     xhci = hcd_to_xhci(dev_get_drvdata(dev));
> > +     return xhci_resume(xhci, hibernated);
> > +}
>
> These won't be required any more as xhci-plat is doing this.
>
> > +#endif /* CONFIG_PM */
> > +
> > +int cdns3_host_init(struct cdns3 *cdns)
> > +{
> > +     struct cdns3_role_driver *rdrv;
> > +
> > +     rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
> > +     if (!rdrv)
> > +             return -ENOMEM;
> > +
> > +     rdrv->start     = cdns3_host_start;
> > +     rdrv->stop      = cdns3_host_stop;
> > +     rdrv->irq       = cdns3_host_irq;
> > +#if CONFIG_PM
> > +     rdrv->suspend   = cdns3_host_suspend;
> > +     rdrv->resume    = cdns3_host_resume;
> > +#endif /* CONFIG_PM */
> > +     rdrv->name      = "host";
> > +     cdns->roles[CDNS3_ROLE_HOST] = rdrv;
> > +
> > +     return 0;
> > +}
> > +
> > +void cdns3_host_remove(struct cdns3 *cdns)
> > +{
> > +     cdns3_host_stop(cdns);
>
> calling cdns3_host_stop() here can lead to problems as Controller might be in
> peripheral mode at this point. The core driver needs to ensure that relevant role
> is stopped before calling cdns3_host_remove().
>
> Here you need to unregister the role driver though.
>
> cdns->roles[CDNS3_ROLE_HOST] = NULL;
>
> > +}
> > +
> > +void __init cdns3_host_driver_init(void)
> > +{
> > +     xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides);
> > +}
> >
>
> cheers,
> -roger
>
> --
> Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
> Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-05  7:19         ` Pawel Laszczak
@ 2018-12-05  8:55           ` Alan Douglas
  2018-12-05  9:07             ` Peter Chen
  0 siblings, 1 reply; 85+ messages in thread
From: Alan Douglas @ 2018-12-05  8:55 UTC (permalink / raw)
  To: Pawel Laszczak, Peter Chen
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	jbergsagel, nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar

Hi Peter,

On 05 December 2018 07:20, Pawel Laszczak wrote:
> Hi,
> 
> >> >>
> >> >> Patch adds core.c and core.h file that implements initialization
> >> >> of platform driver and adds function responsible for selecting,
> >> >> switching and running appropriate Device/Host mode.
> >> >>
> >> >> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> >> >> ---
> >> >>  drivers/usb/cdns3/Makefile |   2 +
> >> >>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
> >> >>  drivers/usb/cdns3/core.h   | 100 +++++++++
> >> >>  3 files changed, 515 insertions(+)
> >> >>  create mode 100644 drivers/usb/cdns3/core.c
> >> >>  create mode 100644 drivers/usb/cdns3/core.h
> >> >>
> >> >> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> >> >> index dcdd62003c6a..02d25b23c5d3 100644
> >> >> --- a/drivers/usb/cdns3/Makefile
> >> >> +++ b/drivers/usb/cdns3/Makefile
> >> >> @@ -1,3 +1,5 @@
> >> >> +obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
> >> >>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
> >> >>
> >> >> +cdns3-y                                        := core.o
> >> >>  cdns3-pci-y                            := cdns3-pci-wrap.o
> >> >> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> >> >> new file mode 100644
> >> >> index 000000000000..f9055d4da67f
> >> >> --- /dev/null
> >> >> +++ b/drivers/usb/cdns3/core.c
> >> >> @@ -0,0 +1,413 @@
> >> >> +// SPDX-License-Identifier: GPL-2.0
> >> >> +/*
> >> >> + * Cadence USBSS DRD Driver.
> >> >> + *
> >> >> + * Copyright (C) 2018 Cadence.
> >> >> + *
> >> >
> >> >Please add NXP copyright too.
> >>
> >> Ok, I don't know why I omitted this.
> >> I know that you are the main author of this file
> >> Sorry for that.
> >>
> >> One additional question. What year I should add in Copyright for NXP?.
> >> The original year 2017 or I should modified all to 2018.
> >>
> >Please use below copyright, thanks.
> >
> >Copyright 2017-2018 NXP
> 
> I add this in all files  modified or created by you.
> >
> >
> >
> >> >> +       mutex_init(&cdns->mutex);
> >> >> +
> >> >> +       cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> >> >> +       if (IS_ERR(cdns->phy)) {
> >> >> +               dev_info(dev, "no generic phy found\n");
> >> >> +               cdns->phy = NULL;
> >> >> +               /*
> >> >> +                * fall through here!
> >> >> +                * if no generic phy found, phy init
> >> >> +                * should be done under boot!
> >> >> +                */
> >> >
> >> >If the phy driver is defer-probed, it will be here, it is not an error.
> >> >I think you could have a generic phy driver or usb generic phy driver
> >> >(drivers/usb/phy/phy-generic.c) even you don't need any operations for
> >> >PHY. It will be easy for other platforms.
> >>
> >> Yes, Roger ask me to modify this fragment. In next version it will look like:
> >>         cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> >>         if (IS_ERR(cdns->phy)) {
> >>                 ret = PTR_ERR(cdns->phy);
> >>                 if (ret == -ENOSYS || ret == -ENODEV) {
> >>                         cdns->phy = NULL;
> >>                 } else if (ret == -EPROBE_DEFER) {
> >>                         return ret;
> >>                 } else {
> >>                         dev_err(dev, "no phy found\n");
> >>                         goto err0;
> >>                 }
> >>         }
> >>
> >>         phy_init(cdns->phy);
> >>
> >> We are going to use phy driver. I don't know if it correct.
> >> I don't have experience in this filed.
> >> We need phy initialization but I don't have testing platform now.
> >> In most usb drivers I see that there are used usb phy driverd instead phy dirverd.
> >>
> >
> >At my CDNS3 platform, there are some USB PHY initialization for register setting
> >and clock enable. You could add generic PHY driver under: drivers/phy/cadence/.
> >
> >Above PHY initialization code is OK for me.
> 
> It will be added as separate driver.
> I think that Allan Douglas working on it.
> I ask him to add you to -cc in patch for phy.
Patch series for the cadence Sierra generic PHY driver can be found here:
https://lore.kernel.org/patchwork/cover/1011486/

It can also be found in Kishon's linux-phy git tree at
    git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy.git
in the 'next' branch.

It will be great if you are able to take a look at it.

Regards,
Alan

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04 10:46       ` Roger Quadros
@ 2018-12-05  8:57         ` Peter Chen
  2018-12-06  9:31         ` Pawel Laszczak
  1 sibling, 0 replies; 85+ messages in thread
From: Peter Chen @ 2018-12-05  8:57 UTC (permalink / raw)
  To: rogerq
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul

>
>
>
> On 04/12/18 10:50, Peter Chen wrote:
> >>> + * Cadence USBSS DRD Driver.
> >>> + *
> >>> + * Copyright (C) 2018 Cadence.
> >>> + *
> >>> + * Author: Peter Chen <peter.chen@nxp.com>
> >>> + *         Pawel Laszczak <pawell@cadence.com>
> >>> + */
> >>> +
> >>> +#include <linux/module.h>
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/platform_device.h>
> >>> +#include <linux/interrupt.h>
> >>> +#include <linux/io.h>
> >>> +#include <linux/pm_runtime.h>
> >>> +
> >>> +#include "gadget.h"
> >>> +#include "core.h"
> >>> +
> >>> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
> >>> +{
> >>> +     WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
> >>> +     return cdns->roles[cdns->role];
> >>> +}
> >>> +
> >>> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
> >>> +{
> >>> +     int ret;
> >>> +
> >>> +     if (role >= CDNS3_ROLE_END)
> >>
> >> WARN_ON()?
> >>
> >>> +             return 0;
> >>> +
> >>> +     if (!cdns->roles[role])
> >>> +             return -ENXIO;
> >>> +
> >>> +     mutex_lock(&cdns->mutex);
> >>> +     cdns->role = role;
> >>> +     ret = cdns->roles[role]->start(cdns);
> >>> +     mutex_unlock(&cdns->mutex);
> >>> +     return ret;
> >>> +}
> >>> +
> >>> +static inline void cdns3_role_stop(struct cdns3 *cdns)
> >>> +{
> >>> +     enum cdns3_roles role = cdns->role;
> >>> +
> >>> +     if (role == CDNS3_ROLE_END)
> >>
> >> WARN_ON(role >= CNDS3_ROLE_END) ?
> >>
> >>> +             return;
> >>> +
> >>> +     mutex_lock(&cdns->mutex);
> >>> +     cdns->roles[role]->stop(cdns);
> >>> +     cdns->role = CDNS3_ROLE_END;
> >>
> >> Why change the role here? You are just stopping the role not changing it.
> >> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
> >> if required without error.
> >>
> >
> > The current version of this IP has some issues to detect vbus status correctly,
> > we have to force vbus status accordingly, so we need a status to indicate
> > vbus disconnection, and add some code to let controller know vbus
> > removal, in that case, the controller's state machine can be correct.
> > So, we increase one role 'CDNS3_ROLE_END' to for this purpose.
> >
> > CDNS3_ROLE_GADGET: gadget mode and VBUS on
> > CDNS3_ROLE_HOST: host mode and VBUS on
> > CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.
> >
> > So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
> > and need to set role as CDNS3_ROLE_END at ->stop for further handling at
> > role switch routine.
>
> OK. but still this (changing to ROLE_END) must be moved to the role switch routine
> and the explanation you just mentioned must be added there.
>

If we use host part as separate driver, something may need to change.
The ROLE_END
may need to add at role switch routine as your said.

> >
> >>> +     mutex_unlock(&cdns->mutex);
> >>> +}
> >>> +
> >>> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
> >>> +{
> >>> +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
> >>> +             //TODO: implements selecting device/host mode
> >>> +             return CDNS3_ROLE_HOST;
> >>> +     }
> >>> +     return cdns->roles[CDNS3_ROLE_HOST]
> >>> +             ? CDNS3_ROLE_HOST
> >>> +             : CDNS3_ROLE_GADGET;
> >>
> >> Why not just
> >>         return cdns->role;
> >>
> >> I'm wondering if we really need this function.
> >
> > cdns->role gets from cdns3_get_role, and this API tells role at the runtime.
> > If both roles are supported, the role is decided by external
> > conditions, eg, vbus/id
> > or external connector. If only single role is supported, only one role structure
> > is allocated, cdns->roles[CDNS3_ROLE_HOST] or cdns->roles[CDNS3_ROLE_GADGET]
> >
>
> How about adding this description in function documentation.
>

Sure, but may need to redesign it due to host part as a standalone driver.


> >>
> >> Then you can move cdns3_role_start() as well to core_init_role().
> >>
> >
> > Usually, we request irq after hardware initialization has finished, if not,
> > there may unexpected interrupt.
>
> Doesn't kernel warn if interrupt happens and there is no handler?
> To avoid that I was suggesting to request_irq first.
>

The interrupt will not happen (related bit at GIC is masked) until we
call request_irq.
And we don't want to handle interrupts until hardware initialization, don't we?

Peter

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

* Re: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-05  8:55           ` Alan Douglas
@ 2018-12-05  9:07             ` Peter Chen
  0 siblings, 0 replies; 85+ messages in thread
From: Peter Chen @ 2018-12-05  9:07 UTC (permalink / raw)
  To: adouglas
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez, kurahul

On Wed, Dec 5, 2018 at 4:55 PM Alan Douglas <adouglas@cadence.com> wrote:
>
> Hi Peter,
>
> On 05 December 2018 07:20, Pawel Laszczak wrote:
> > Hi,
> >
> > >> >>
> > >> >> Patch adds core.c and core.h file that implements initialization
> > >> >> of platform driver and adds function responsible for selecting,
> > >> >> switching and running appropriate Device/Host mode.
> > >> >>
> > >> >> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
> > >> >> ---
> > >> >>  drivers/usb/cdns3/Makefile |   2 +
> > >> >>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
> > >> >>  drivers/usb/cdns3/core.h   | 100 +++++++++
> > >> >>  3 files changed, 515 insertions(+)
> > >> >>  create mode 100644 drivers/usb/cdns3/core.c
> > >> >>  create mode 100644 drivers/usb/cdns3/core.h
> > >> >>
> > >> >> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
> > >> >> index dcdd62003c6a..02d25b23c5d3 100644
> > >> >> --- a/drivers/usb/cdns3/Makefile
> > >> >> +++ b/drivers/usb/cdns3/Makefile
> > >> >> @@ -1,3 +1,5 @@
> > >> >> +obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
> > >> >>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
> > >> >>
> > >> >> +cdns3-y                                        := core.o
> > >> >>  cdns3-pci-y                            := cdns3-pci-wrap.o
> > >> >> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
> > >> >> new file mode 100644
> > >> >> index 000000000000..f9055d4da67f
> > >> >> --- /dev/null
> > >> >> +++ b/drivers/usb/cdns3/core.c
> > >> >> @@ -0,0 +1,413 @@
> > >> >> +// SPDX-License-Identifier: GPL-2.0
> > >> >> +/*
> > >> >> + * Cadence USBSS DRD Driver.
> > >> >> + *
> > >> >> + * Copyright (C) 2018 Cadence.
> > >> >> + *
> > >> >
> > >> >Please add NXP copyright too.
> > >>
> > >> Ok, I don't know why I omitted this.
> > >> I know that you are the main author of this file
> > >> Sorry for that.
> > >>
> > >> One additional question. What year I should add in Copyright for NXP?.
> > >> The original year 2017 or I should modified all to 2018.
> > >>
> > >Please use below copyright, thanks.
> > >
> > >Copyright 2017-2018 NXP
> >
> > I add this in all files  modified or created by you.
> > >
> > >
> > >
> > >> >> +       mutex_init(&cdns->mutex);
> > >> >> +
> > >> >> +       cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> > >> >> +       if (IS_ERR(cdns->phy)) {
> > >> >> +               dev_info(dev, "no generic phy found\n");
> > >> >> +               cdns->phy = NULL;
> > >> >> +               /*
> > >> >> +                * fall through here!
> > >> >> +                * if no generic phy found, phy init
> > >> >> +                * should be done under boot!
> > >> >> +                */
> > >> >
> > >> >If the phy driver is defer-probed, it will be here, it is not an error.
> > >> >I think you could have a generic phy driver or usb generic phy driver
> > >> >(drivers/usb/phy/phy-generic.c) even you don't need any operations for
> > >> >PHY. It will be easy for other platforms.
> > >>
> > >> Yes, Roger ask me to modify this fragment. In next version it will look like:
> > >>         cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
> > >>         if (IS_ERR(cdns->phy)) {
> > >>                 ret = PTR_ERR(cdns->phy);
> > >>                 if (ret == -ENOSYS || ret == -ENODEV) {
> > >>                         cdns->phy = NULL;
> > >>                 } else if (ret == -EPROBE_DEFER) {
> > >>                         return ret;
> > >>                 } else {
> > >>                         dev_err(dev, "no phy found\n");
> > >>                         goto err0;
> > >>                 }
> > >>         }
> > >>
> > >>         phy_init(cdns->phy);
> > >>
> > >> We are going to use phy driver. I don't know if it correct.
> > >> I don't have experience in this filed.
> > >> We need phy initialization but I don't have testing platform now.
> > >> In most usb drivers I see that there are used usb phy driverd instead phy dirverd.
> > >>
> > >
> > >At my CDNS3 platform, there are some USB PHY initialization for register setting
> > >and clock enable. You could add generic PHY driver under: drivers/phy/cadence/.
> > >
> > >Above PHY initialization code is OK for me.
> >
> > It will be added as separate driver.
> > I think that Allan Douglas working on it.
> > I ask him to add you to -cc in patch for phy.
> Patch series for the cadence Sierra generic PHY driver can be found here:
> https://lore.kernel.org/patchwork/cover/1011486/
>
> It can also be found in Kishon's linux-phy git tree at
>     git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy.git
> in the 'next' branch.
>
> It will be great if you are able to take a look at it.

I will test it, and submit some changes if needed, thanks.

Peter

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04  8:50     ` Peter Chen
  2018-12-04 10:46       ` Roger Quadros
@ 2018-12-05 19:24       ` Pawel Laszczak
  2018-12-05 19:42       ` Pawel Laszczak
  2 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-05 19:24 UTC (permalink / raw)
  To: Peter Chen, rogerq
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, lkml, Alan Douglas,
	jbergsagel, nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar

Hi

+ Tomek

>> > + * Cadence USBSS DRD Driver.
>> > + *
>> > + * Copyright (C) 2018 Cadence.
>> > + *
>> > + * Author: Peter Chen <peter.chen@nxp.com>
>> > + *         Pawel Laszczak <pawell@cadence.com>
>> > + */
>> > +
>> > +#include <linux/module.h>
>> > +#include <linux/kernel.h>
>> > +#include <linux/platform_device.h>
>> > +#include <linux/interrupt.h>
>> > +#include <linux/io.h>
>> > +#include <linux/pm_runtime.h>
>> > +
>> > +#include "gadget.h"
>> > +#include "core.h"
>> > +
>> > +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>> > +{
>> > +     WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
>> > +     return cdns->roles[cdns->role];
>> > +}
>> > +
>> > +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
>> > +{
>> > +     int ret;
>> > +
>> > +     if (role >= CDNS3_ROLE_END)
>>
>> WARN_ON()?
>>
>> > +             return 0;
>> > +
>> > +     if (!cdns->roles[role])
>> > +             return -ENXIO;
>> > +
>> > +     mutex_lock(&cdns->mutex);
>> > +     cdns->role = role;
>> > +     ret = cdns->roles[role]->start(cdns);
>> > +     mutex_unlock(&cdns->mutex);
>> > +     return ret;
>> > +}
>> > +
>> > +static inline void cdns3_role_stop(struct cdns3 *cdns)
>> > +{
>> > +     enum cdns3_roles role = cdns->role;
>> > +
>> > +     if (role == CDNS3_ROLE_END)
>>
>> WARN_ON(role >= CNDS3_ROLE_END) ?
>>
>> > +             return;
>> > +
>> > +     mutex_lock(&cdns->mutex);
>> > +     cdns->roles[role]->stop(cdns);
>> > +     cdns->role = CDNS3_ROLE_END;
>>
>> Why change the role here? You are just stopping the role not changing it.
>> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>> if required without error.
>>
>
>The current version of this IP has some issues to detect vbus status correctly,
>we have to force vbus status accordingly, so we need a status to indicate
>vbus disconnection, and add some code to let controller know vbus
>removal, in that case, the controller's state machine can be correct.
>So, we increase one role 'CDNS3_ROLE_END' to for this purpose.
>
>CDNS3_ROLE_GADGET: gadget mode and VBUS on
>CDNS3_ROLE_HOST: host mode and VBUS on
>CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.

>
>So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
>and need to set role as CDNS3_ROLE_END at ->stop for further handling at
>role switch routine.
>
>> > +     mutex_unlock(&cdns->mutex);
>> > +}
>> > +
>> > +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>> > +{
>> > +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> > +             //TODO: implements selecting device/host mode
>> > +             return CDNS3_ROLE_HOST;
>> > +     }
>> > +     return cdns->roles[CDNS3_ROLE_HOST]
>> > +             ? CDNS3_ROLE_HOST
>> > +             : CDNS3_ROLE_GADGET;
>>
>> Why not just
>>         return cdns->role;
>>
>> I'm wondering if we really need this function.
>
>cdns->role gets from cdns3_get_role, and this API tells role at the runtime.
>If both roles are supported, the role is decided by external
>conditions, eg, vbus/id
>or external connector. If only single role is supported, only one role structure
>is allocated, cdns->roles[CDNS3_ROLE_HOST] or cdns->roles[CDNS3_ROLE_GADGET]
>
>> > +}
>>
>> > +
>> > +/**
>> > + * cdns3_core_init_role - initialize role of operation
>> > + * @cdns: Pointer to cdns3 structure
>> > + *
>> > + * Returns 0 on success otherwise negative errno
>> > + */
>> > +static int cdns3_core_init_role(struct cdns3 *cdns)
>> > +{
>> > +     struct device *dev = cdns->dev;
>> > +     enum usb_dr_mode dr_mode;
>> > +
>> > +     dr_mode = usb_get_dr_mode(dev);
>> > +     cdns->role = CDNS3_ROLE_END;
>> > +
>> > +     /*
>> > +      * If driver can't read mode by means of usb_get_dr_mdoe function then
>> > +      * chooses mode according with Kernel configuration. This setting
>> > +      * can be restricted later depending on strap pin configuration.
>> > +      */
>> > +     if (dr_mode == USB_DR_MODE_UNKNOWN) {
>> > +             if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
>> > +                 IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> > +                     dr_mode = USB_DR_MODE_OTG;
>> > +             else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
>> > +                     dr_mode = USB_DR_MODE_HOST;
>> > +             else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> > +                     dr_mode = USB_DR_MODE_PERIPHERAL;
>> > +     }
>> > +
>> > +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>> > +             //TODO: implements host initialization
>>
>>                 /* TODO: Add host role */ ?
>>
>> > +     }
>> > +
>> > +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>> > +             //TODO: implements device initialization
>>
>>                 /* TODO: Add device role */ ?
>>
>
>Yes, it needs to allocate cdns->roles[CDNS3_ROLE_HOST] and
>cdns->roles[CDNS3_ROLE_GADGET].
>
>> > +     }
>> > +
>> > +     if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>> > +             dev_err(dev, "no supported roles\n");
>> > +             return -ENODEV;
>> > +     }
>> > +
>> > +     cdns->dr_mode = dr_mode;
>
>Pawel, why dr_mode needs to be introduced?

cdns->dr_mode - it's the mode depending on configuration. 
We have device tree from which we can read dr_mode. It can be Host, Device or OTG - the maximum is OTG.
Additionally we have  strap pin that allow to force selected mode. 
Additionally mode can be limited by kernel configuration. 

So, I assume that controller can support OTG (HOST+DEVICE) , but it can be restricted by kernel configuration, strap pin or DTS (dr_mode property). 

When this configuration is restricted by one of these then if dr_mode is :
Host - we simply load XHCI driver
Device  - we start Device part
OTG - we based on ID pin

Eg. 
Controller support OTG mode but STRAP  is set to Host only then  dr_mode is set to  host
Controller support OTG mode but STRAP  is set to Device only then  dr_mode is set  to device 
Controller support OTG mode and STRAP  is set to OTG then  dr_mode is set  to OTG and  it can support Device only, Host only or OTG mode.

Additionally I'm introduce dr_mode that can be changed from user space. It can use for testing purpose. 
So even dr_mode  = OTG then we can force switching role from user space. 

I'm using it for testing. 

So additionally in cdns3 object we have desired_dr_mode and current_dr_mode. 

>
>> > +     return 0;
>> > +}
>> > +
>> > +/**
>> > + * cdns3_irq - interrupt handler for cdns3 core device
>> > + *
>> > + * @irq: irq number for cdns3 core device
>> > + * @data: structure of cdns3
>> > + *
>> > + * Returns IRQ_HANDLED or IRQ_NONE
>> > + */
>> > +static irqreturn_t cdns3_irq(int irq, void *data)
>> > +{
>> > +     struct cdns3 *cdns = data;
>> > +     irqreturn_t ret = IRQ_NONE;
>> > +
>> > +     /* Handle device/host interrupt */
>> > +     if (cdns->role != CDNS3_ROLE_END)
>>
>> Is it because of this that you need to set role to END at role_stop?
>> I think it is better to add a state variable to struct cdns3_role_driver, so we can
>> check if it is active or stopped.
>>
>> e.g.
>>         if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)
>>
>> > +             ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>> > +
>> > +     return ret;
>> > +}
>> > +
>
> CDNS3_ROLE_END is introduced from above comments, we don't
>need another flag for it.
>If cdns->role == CDNS3_ROLE_END, it handles VBUS and ID interrupt.
>
>> > +static void cdns3_remove_roles(struct cdns3 *cdns)
>>
>> Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?
>>
>
>It is planed to called when at ->remove.
>> > +{
>> > +     //TODO: implements this function
>> > +}
>>
>> > +
>> > +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>> > +{
>> > +     enum cdns3_roles current_role;
>> > +     int ret = 0;
>> > +
>> > +     current_role = cdns->role;
>> > +
>> > +     if (role == CDNS3_ROLE_END)
>> > +             return 0;
>>
>> role == END looks like error state. and it should never happen.
>> WARN here?
>>
>
>See my comments above.
>
>> > +
>> > +     dev_dbg(cdns->dev, "Switching role");
>> > +
>>
>> Don't you have to stop the previous role before starting the new role?
>>
>
>Yes, it is needed. Pawel may simply some flows to suit his platform.

>
>> > +     ret = cdns3_role_start(cdns, role);
>> > +     if (ret) {
>> > +             /* Back to current role */
>> > +             dev_err(cdns->dev, "set %d has failed, back to %d\n",
>> > +                     role, current_role);
>> > +             ret = cdns3_role_start(cdns, current_role);
>> > +     }
>> > +
>> > +     return ret;
>> > +}
>> > +
>> > +/**
>> > + * cdns3_role_switch - work queue handler for role switch
>> > + *
>> > + * @work: work queue item structure
>> > + *
>> > + * Handles below events:
>> > + * - Role switch for dual-role devices
>> > + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
>> > + */
>> > +static void cdns3_role_switch(struct work_struct *work)
>> > +{
>> > +     enum cdns3_roles role = CDNS3_ROLE_END;
>> > +     struct cdns3 *cdns;
>> > +     bool device, host;
>> > +
>> > +     cdns = container_of(work, struct cdns3, role_switch_wq);
>> > +
>> > +     //TODO: implements this functions.
>> > +     //host = cdns3_is_host(cdns);
>> > +     //device = cdns3_is_device(cdns);
>> > +     host = 1;
>> > +     device = 0;
>> > +
>> > +     if (host)
>> > +             role = CDNS3_ROLE_HOST;
>> > +     else if (device)
>> > +             role = CDNS3_ROLE_GADGET;
>> > +
>> > +     if (cdns->desired_dr_mode == cdns->current_dr_mode &&
>> > +         cdns->role == role)
>> > +             return;
>> > +
>>
>> I think all the below code can be moved to cdns3_do_role_switch().
>>
>> > +     pm_runtime_get_sync(cdns->dev);
>> > +     cdns3_role_stop(cdns);
>> > +
>> > +     if (host) {
>> > +             if (cdns->roles[CDNS3_ROLE_HOST])
>> > +                     cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>> > +             pm_runtime_put_sync(cdns->dev);
>> > +             return;
>> > +     }
>> > +
>> > +     if (device)
>> > +             cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
>> > +     else
>> > +             cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
>> > +
>> > +     pm_runtime_put_sync(cdns->dev);
>> > +}
>> > +
>> > +/**
>> > + * cdns3_probe - probe for cdns3 core device
>> > + * @pdev: Pointer to cdns3 core platform device
>> > + *
>> > + * Returns 0 on success otherwise negative errno
>> > + */
>> > +static int cdns3_probe(struct platform_device *pdev)
>> > +{
>> > +     struct device *dev = &pdev->dev;
>> > +     struct resource *res;
>> > +     struct cdns3 *cdns;
>> > +     void __iomem *regs;
>> > +     int ret;
>> > +
>> > +     cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
>> > +     if (!cdns)
>> > +             return -ENOMEM;
>> > +
>> > +     cdns->dev = dev;
>> > +
>> > +     platform_set_drvdata(pdev, cdns);
>> > +
>> > +     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
>> > +     if (!res) {
>> > +             dev_err(dev, "missing IRQ\n");
>> > +             return -ENODEV;
>> > +     }
>> > +     cdns->irq = res->start;
>> > +
>> > +     /*
>> > +      * Request memory region
>> > +      * region-0: xHCI
>> > +      * region-1: Peripheral
>> > +      * region-2: OTG registers
>> > +      */
>>
>> The memory region order is different from the dt-binding.
>> There it is OTG, host(xhci), device (peripheral).
>>
>> > +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> > +     regs = devm_ioremap_resource(dev, res);
>> > +
>> > +     if (IS_ERR(regs))
>> > +             return PTR_ERR(regs);
>> > +     cdns->xhci_regs = regs;
>> > +     cdns->xhci_res = res;
>> > +
>> > +     res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> > +     regs = devm_ioremap_resource(dev, res);
>> > +     if (IS_ERR(regs))
>> > +             return PTR_ERR(regs);
>> > +     cdns->dev_regs  = regs;
>> > +
>> > +     res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>> > +     regs = devm_ioremap_resource(dev, res);
>> > +     if (IS_ERR(regs))
>> > +             return PTR_ERR(regs);
>> > +     cdns->otg_regs = regs;
>> > +
>> > +     mutex_init(&cdns->mutex);
>> > +
>> > +     cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>>
>> "cdns3,usbphy" is not documented in dt-binding.
>>
>> > +     if (IS_ERR(cdns->phy)) {
>> > +             dev_info(dev, "no generic phy found\n");
>> > +             cdns->phy = NULL;
>> > +             /*
>> > +              * fall through here!
>> > +              * if no generic phy found, phy init
>> > +              * should be done under boot!
>> > +              */
>>
>> No you shouldn't fall through always if it is an error condition.
>> Something like this should work better.
>>
>>         if (IS_ERR(cnds->phy)) {
>>                 ret = PTR_ERR(cdns->phy);
>>                 if (ret == -ENOSYS || ret == -ENODEV) {
>>                         cdns->phy = NULL;
>>                 } else if (ret == -EPROBE_DEFER) {
>>                         return ret;
>>                 } else {
>>                         dev_err(dev, "no phy found\n");
>>                         goto err0;
>>                 }
>>         }
>>
>> So if PHY was provided in DT, and PHY support/drivers is present
>> and error condition means something is wrong and we have to error out.
>>
>> > +     } else {
>> > +             phy_init(cdns->phy);
>> > +     }
>>
>> You can do phy_init() outside the else.
>>
>> > +
>> > +     ret = cdns3_core_init_role(cdns);
>> > +     if (ret)
>> > +             goto err1;
>> > +
>> > +     INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
>> > +     if (ret)
>> > +             goto err2;
>> > +
>> > +     if (ret)
>> > +             goto err2;
>> > +
>> > +     cdns->role = cdns3_get_role(cdns);
>>
>> I think this should move to cdns3_core_init_role().
>>
>
>I agree.
>
>> > +
>> > +     ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
>> > +                            dev_name(dev), cdns);
>> > +
>> > +     if (ret)
>> > +             goto err2;
>>
>> How about moving request_irq to before cdsn3_core_init_role()?
>>
>> Then you can move cdns3_role_start() as well to core_init_role().
>>
>
>Usually, we request irq after hardware initialization has finished, if not,
>there may unexpected interrupt.

>
>Peter
>
>> > +
>> > +     ret = cdns3_role_start(cdns, cdns->role);
>> > +     if (ret) {
>> > +             dev_err(dev, "can't start %s role\n",
>> > +                     cdns3_get_current_role_driver(cdns)->name);
>> > +             goto err2;
>> > +     }
>> > +
>> > +     device_set_wakeup_capable(dev, true);
>> > +     pm_runtime_set_active(dev);
>> > +     pm_runtime_enable(dev);
>> > +
>> > +     /*
>> > +      * The controller needs less time between bus and controller suspend,
>> > +      * and we also needs a small delay to avoid frequently entering low
>> > +      * power mode.
>> > +      */
>> > +     pm_runtime_set_autosuspend_delay(dev, 20);
>> > +     pm_runtime_mark_last_busy(dev);
>> > +     pm_runtime_use_autosuspend(dev);
>> > +     dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
>> > +
>> > +     return 0;
>> > +
>> > +err2:
>> > +     cdns3_remove_roles(cdns);
>> > +err1:
>>
>> phy_exit() ?
>>
>> > +     return ret;
>> > +}
>> > +
>> > +/**
>> > + * cdns3_remove - unbind drd driver and clean up
>> > + * @pdev: Pointer to Linux platform device
>> > + *
>> > + * Returns 0 on success otherwise negative errno
>> > + */
>> > +static int cdns3_remove(struct platform_device *pdev)
>> > +{
>> > +     struct cdns3 *cdns = platform_get_drvdata(pdev);
>> > +
>> > +     pm_runtime_get_sync(&pdev->dev);
>> > +     pm_runtime_disable(&pdev->dev);
>> > +     pm_runtime_put_noidle(&pdev->dev);
>> > +     cdns3_remove_roles(cdns);
>>
>> phy_exit() ?
>>
>> > +
>> > +     return 0;
>> > +}
>> > +
>> > +#ifdef CONFIG_OF
>> > +static const struct of_device_id of_cdns3_match[] = {
>> > +     { .compatible = "cdns,usb3" },
>> > +     { },
>> > +};
>> > +MODULE_DEVICE_TABLE(of, of_cdns3_match);
>> > +#endif
>> > +
>> > +#ifdef CONFIG_PM
>> > +
>> > +#ifdef CONFIG_PM_SLEEP
>> > +static int cdns3_suspend(struct device *dev)
>> > +{
>> > +     //TODO: implements this function
>> > +     return 0;
>> > +}
>> > +
>> > +static int cdns3_resume(struct device *dev)
>> > +{
>> > +     //TODO: implements this function
>> > +     return 0;
>> > +}
>> > +#endif /* CONFIG_PM_SLEEP */
>> > +static int cdns3_runtime_suspend(struct device *dev)
>> > +{    //TODO: implements this function
>> > +     return 0;
>> > +}
>> > +
>> > +static int cdns3_runtime_resume(struct device *dev)
>> > +{
>> > +     //TODO: implements this function
>> > +     return 0;
>> > +}
>> > +#endif /* CONFIG_PM */
>> > +
>> > +static const struct dev_pm_ops cdns3_pm_ops = {
>> > +     SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
>> > +     SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
>> > +};
>> > +
>> > +static struct platform_driver cdns3_driver = {
>> > +     .probe          = cdns3_probe,
>> > +     .remove         = cdns3_remove,
>> > +     .driver         = {
>> > +             .name   = "cdns-usb3",
>> > +             .of_match_table = of_match_ptr(of_cdns3_match),
>> > +             .pm     = &cdns3_pm_ops,
>> > +     },
>> > +};
>> > +
>> > +static int __init cdns3_driver_platform_register(void)
>> > +{
>> > +     return platform_driver_register(&cdns3_driver);
>> > +}
>> > +module_init(cdns3_driver_platform_register);
>> > +
>> > +static void __exit cdns3_driver_platform_unregister(void)
>> > +{
>> > +     platform_driver_unregister(&cdns3_driver);
>> > +}
>> > +module_exit(cdns3_driver_platform_unregister);
>> > +
>> > +MODULE_ALIAS("platform:cdns3");
>> > +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
>> > +MODULE_LICENSE("GPL v2");
>> > +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
>> > diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
>> > new file mode 100644
>> > index 000000000000..7c8204fe4d3d
>> > --- /dev/null
>> > +++ b/drivers/usb/cdns3/core.h
>> > @@ -0,0 +1,100 @@
>> > +/* SPDX-License-Identifier: GPL-2.0 */
>> > +/*
>> > + * Cadence USBSS DRD Driver.
>> > + *
>> > + * Copyright (C) 2017 NXP
>> > + * Copyright (C) 2018 Cadence.
>> > + *
>> > + * Authors: Peter Chen <peter.chen@nxp.com>
>> > + *          Pawel Laszczak <pawell@cadence.com>
>> > + */
>> > +#include <linux/usb/otg.h>
>> > +
>> > +#ifndef __LINUX_CDNS3_CORE_H
>> > +#define __LINUX_CDNS3_CORE_H
>> > +
>> > +struct cdns3;
>> > +enum cdns3_roles {
>> > +     CDNS3_ROLE_HOST = 0,
>> > +     CDNS3_ROLE_GADGET,
>> > +     CDNS3_ROLE_END,
>> > +};
>> > +
>> > +/**
>> > + * struct cdns3_role_driver - host/gadget role driver
>> > + * @start: start this role
>> > + * @stop: stop this role
>> > + * @suspend: suspend callback for this role
>> > + * @resume: resume callback for this role
>> > + * @irq: irq handler for this role
>> > + * @name: role name string (host/gadget)
>> > + */
>> > +struct cdns3_role_driver {
>> > +     int (*start)(struct cdns3 *cdns);
>> > +     void (*stop)(struct cdns3 *cdns);
>> > +     int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
>> > +     int (*resume)(struct cdns3 *cdns, bool hibernated);
>> > +     irqreturn_t (*irq)(struct cdns3 *cdns);
>> > +     const char *name;
>> > +};
>> > +
>> > +#define CDNS3_NUM_OF_CLKS    5
>> > +/**
>> > + * struct cdns3 - Representation of Cadence USB3 DRD controller.
>> > + * @dev: pointer to Cadence device struct
>> > + * @xhci_regs: pointer to base of xhci registers
>> > + * @xhci_res: the resource for xhci
>> > + * @dev_regs: pointer to base of dev registers
>> > + * @otg_regs: pointer to base of otg registers
>> > + * @irq: irq number for controller
>> > + * @roles: array of supported roles for this controller
>> > + * @role: current role
>> > + * @host_dev: the child host device pointer for cdns3 core
>> > + * @gadget_dev: the child gadget device pointer for cdns3 core
>> > + * @usb: phy for this controller
>> > + * @role_switch_wq: work queue item for role switch
>> > + * @in_lpm: the controller in low power mode
>> > + * @wakeup_int: the wakeup interrupt
>> > + * @mutex: the mutex for concurrent code at driver
>> > + * @dr_mode: supported mode of operation it can be only Host, only Device
>> > + *           or OTG mode that allow to switch between Device and Host mode.
>> > + *           This field based on hardware configuration and cant't be changed.
>>
>> But dr_mode can be forced in device-tree. So it isn't really only hardware configuration.
>>
>> > + * @current_dr_role: current mode of operation when in dual-role mode
>> > + * @desired_dr_role: desired mode of operation when in dual-role mode.
>> > + *           This value can be changed during runtime.
>> > + *           Available options depends on  dr_mode:
>> > + *           dr_mode                 |  desired_dr_role and current_dr_role
>> > + *           ----------------------------------------------------------------
>> > + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
>> > + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
>> > + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
>> > + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
>> > + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
>>
>> Do you need to update the right hand side to reflect ROLEs instead of MODE?
>>
>> > + *
>> > + *           Desired_dr_role can be changed by means of debugfs.
>> > + * @root: debugfs root folder pointer
>> > + */
>> > +struct cdns3 {
>> > +     struct device                   *dev;
>> > +     void __iomem                    *xhci_regs;
>> > +     struct resource                 *xhci_res;
>> > +     struct cdns3_usb_regs __iomem   *dev_regs;
>> > +     struct cdns3_otg_regs           *otg_regs;
>> > +     int irq;
>> > +     struct cdns3_role_driver        *roles[CDNS3_ROLE_END];
>> > +     enum cdns3_roles                role;
>> > +     struct device                   *host_dev;
>> > +     struct device                   *gadget_dev;
>> > +     struct phy                      *phy;
>> > +     struct work_struct              role_switch_wq;
>> > +     int                             in_lpm:1;
>> > +     int                             wakeup_int:1;
>> > +     /* mutext used in workqueue*/
>> > +     struct mutex                    mutex;
>> > +     enum usb_dr_mode                dr_mode;
>> > +     enum usb_dr_mode                current_dr_mode;
>> > +     enum usb_dr_mode                desired_dr_mode;
>> > +     struct dentry                   *root;
>> > +};
>> > +
>> > +#endif /* __LINUX_CDNS3_CORE_H */
>> >
>>
>> cheers,
>> -roger
>> --
>> Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>> Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04  8:50     ` Peter Chen
  2018-12-04 10:46       ` Roger Quadros
  2018-12-05 19:24       ` Pawel Laszczak
@ 2018-12-05 19:42       ` Pawel Laszczak
  2018-12-06 10:02         ` Pawel Laszczak
  2 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-05 19:42 UTC (permalink / raw)
  To: Peter Chen, rogerq
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, lkml, Alan Douglas,
	jbergsagel, nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar, Tomasz Klimek

+ Tomek Klimek 

>> > +
>> > +static inline void cdns3_role_stop(struct cdns3 *cdns)
>> > +{
>> > +     enum cdns3_roles role = cdns->role;
>> > +
>> > +     if (role == CDNS3_ROLE_END)
>>
>> WARN_ON(role >= CNDS3_ROLE_END) ?
>>
>> > +             return;
>> > +
>> > +     mutex_lock(&cdns->mutex);
>> > +     cdns->roles[role]->stop(cdns);
>> > +     cdns->role = CDNS3_ROLE_END;
>>
>> Why change the role here? You are just stopping the role not changing it.
>> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>> if required without error.
>>
>
>The current version of this IP has some issues to detect vbus status correctly,
>we have to force vbus status accordingly, so we need a status to indicate
>vbus disconnection, and add some code to let controller know vbus
>removal, in that case, the controller's state machine can be correct.
>So, we increase one role 'CDNS3_ROLE_END' to for this purpose.

Hi, Tomek do you have any comment for this.
We have in RTL whole OTG machine and we can read all states. 

From otg specification we have in otg_state such bits:
5:3	host_otg_state	"Current state of the OTG Host FSM.
3'b000 : H_IDLE
3'b001 : H_VBUS_ON
3'b010 : H_VBUS_FAILED
3'b011 : H_OTG_HOST_MODE
3'b100 : H_HOST_MODE
3'b101 : H_SWITCH_TO_DEVICE
3'b110 : H_A_SUSPEND
3'b111 : H_WAIT_VBUS_FALL"	RO	0x0
2:0	dev_otg_state	"Current state of the OTG Device FSM.
3'b000 : DEV_IDLE
3'b001 : DEV_MODE
3'b010 : DEV_SRP
3'b011 : DEV_WAIT_VBUS_FALL
3'b100 : DEV_SWITCH_TO_HOST
3'b101 : DEV_WAIT_FOR_CONN"

>
>CDNS3_ROLE_GADGET: gadget mode and VBUS on
>CDNS3_ROLE_HOST: host mode and VBUS on
>CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.
>
>So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
>and need to set role as CDNS3_ROLE_END at ->stop for further handling at
>role switch routine.
>

>> > +     mutex_unlock(&cdns->mutex);
>> > +}
>> > +
>> > +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>> > +{
>> > +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> > +             //TODO: implements selecting device/host mode
>> > +             return CDNS3_ROLE_HOST;
>> > +     }
>> > +     return cdns->roles[CDNS3_ROLE_HOST]
>> > +             ? CDNS3_ROLE_HOST
>> > +             : CDNS3_ROLE_GADGET;

Cheers
Pawel,

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04  9:09       ` Peter Chen
@ 2018-12-06  7:00         ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-06  7:00 UTC (permalink / raw)
  To: Peter Chen
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	Alan Douglas, jbergsagel, nsekhar, nm, Suresh Punnoose,
	peter.chen, Pawel Jez, Rahul Kumar

>
>> Hi Roger
>>
>> >On 18/11/18 12:09, Pawel Laszczak wrote:
>> >> Patch adds core.c and core.h file that implements initialization
>> >> of platform driver and adds function responsible for selecting,
>> >> switching and running appropriate Device/Host mode.
>> >>
>> >> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> >> ---
>> >>  drivers/usb/cdns3/Makefile |   2 +
>> >>  drivers/usb/cdns3/core.c   | 413 +++++++++++++++++++++++++++++++++++++
>> >>  drivers/usb/cdns3/core.h   | 100 +++++++++
>> >>  3 files changed, 515 insertions(+)
>> >>  create mode 100644 drivers/usb/cdns3/core.c
>> >>  create mode 100644 drivers/usb/cdns3/core.h
>> >>
>> >> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> >> index dcdd62003c6a..02d25b23c5d3 100644
>> >> --- a/drivers/usb/cdns3/Makefile
>> >> +++ b/drivers/usb/cdns3/Makefile
>> >> @@ -1,3 +1,5 @@
>> >> +obj-$(CONFIG_USB_CDNS3)                     += cdns3.o
>> >>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)    += cdns3-pci.o
>> >>
>> >> +cdns3-y                                     := core.o
>> >>  cdns3-pci-y                         := cdns3-pci-wrap.o
>> >> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> >> new file mode 100644
>> >> index 000000000000..f9055d4da67f
>> >> --- /dev/null
>> >> +++ b/drivers/usb/cdns3/core.c
>> >> @@ -0,0 +1,413 @@
>> >> +// SPDX-License-Identifier: GPL-2.0
>> >> +/*
>> >> + * Cadence USBSS DRD Driver.
>> >> + *
>> >> + * Copyright (C) 2018 Cadence.
>> >> + *
>> >> + * Author: Peter Chen <peter.chen@nxp.com>
>> >> + *         Pawel Laszczak <pawell@cadence.com>
>> >> + */
>> >> +
>> >> +#include <linux/module.h>
>> >> +#include <linux/kernel.h>
>> >> +#include <linux/platform_device.h>
>> >> +#include <linux/interrupt.h>
>> >> +#include <linux/io.h>
>> >> +#include <linux/pm_runtime.h>
>> >> +
>> >> +#include "gadget.h"
>> >> +#include "core.h"
>> >> +
>> >> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>> >> +{
>> >> +    WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
>> >> +    return cdns->roles[cdns->role];
>> >> +}
>> >> +
>> >> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
>> >> +{
>> >> +    int ret;
>> >> +
>> >> +    if (role >= CDNS3_ROLE_END)
>> >
>> >WARN_ON()?
>> I agree.
>> >
>> >> +            return 0;
>> >> +
>> >> +    if (!cdns->roles[role])
>> >> +            return -ENXIO;
>> >> +
>> >> +    mutex_lock(&cdns->mutex);
>> >> +    cdns->role = role;
>> >> +    ret = cdns->roles[role]->start(cdns);
>> >> +    mutex_unlock(&cdns->mutex);
>> >> +    return ret;
>> >> +}
>> >> +
>> >> +static inline void cdns3_role_stop(struct cdns3 *cdns)
>> >> +{
>> >> +    enum cdns3_roles role = cdns->role;
>> >> +
>> >> +    if (role == CDNS3_ROLE_END)
>> >
>> >WARN_ON(role >= CNDS3_ROLE_END) ?
>> I agree
>> >
>> >> +            return;
>> >> +
>> >> +    mutex_lock(&cdns->mutex);
>> >> +    cdns->roles[role]->stop(cdns);
>> >> +    cdns->role = CDNS3_ROLE_END;
>> >
>> >Why change the role here? You are just stopping the role not changing it.
>> >I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>> >if required without error.
>>
>> This line is unnecessary.
>>
>> >
>> >> +    mutex_unlock(&cdns->mutex);
>> >> +}
>> >> +
>> >> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>> >> +{
>> >> +    if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> >> +            //TODO: implements selecting device/host mode
>> >> +            return CDNS3_ROLE_HOST;
>> >> +    }
>> >> +    return cdns->roles[CDNS3_ROLE_HOST]
>> >> +            ? CDNS3_ROLE_HOST
>> >> +            : CDNS3_ROLE_GADGET;
>> >
>> >Why not just
>> >       return cdns->role;
>> >
>> >I'm wondering if we really need this function
>>
>> TODO will look likie:
>>                 if (cdns3_is_host(cdns))
>>                         return CDNS3_ROLE_HOST;
>>                 if (cdns3_is_device(cdns))
>>                         return CDNS3_ROLE_GADGET;
>>
>> Function selects initial role. Before invoking it the role is unknown.
>> I think that function name should be  changed because current name can be misleading.
>>
>> I will change it to cdns3_get_initial_role.
>> .
>> >> +}
>> >
>> >> +
>> >> +/**
>> >> + * cdns3_core_init_role - initialize role of operation
>> >> + * @cdns: Pointer to cdns3 structure
>> >> + *
>> >> + * Returns 0 on success otherwise negative errno
>> >> + */
>> >> +static int cdns3_core_init_role(struct cdns3 *cdns)
>> >> +{
>> >> +    struct device *dev = cdns->dev;
>> >> +    enum usb_dr_mode dr_mode;
>> >> +
>> >> +    dr_mode = usb_get_dr_mode(dev);
>> >> +    cdns->role = CDNS3_ROLE_END;
>> >> +
>> >> +    /*
>> >> +     * If driver can't read mode by means of usb_get_dr_mdoe function then
>> >> +     * chooses mode according with Kernel configuration. This setting
>> >> +     * can be restricted later depending on strap pin configuration.
>> >> +     */
>> >> +    if (dr_mode == USB_DR_MODE_UNKNOWN) {
>> >> +            if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
>> >> +                IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> >> +                    dr_mode = USB_DR_MODE_OTG;
>> >> +            else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
>> >> +                    dr_mode = USB_DR_MODE_HOST;
>> >> +            else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>> >> +                    dr_mode = USB_DR_MODE_PERIPHERAL;
>> >> +    }
>> >> +
>> >> +    if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>> >> +            //TODO: implements host initialization
>> >
>> >               /* TODO: Add host role */ ?
>> >
>> >> +    }
>> >> +
>> >> +    if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>> >> +            //TODO: implements device initialization
>> >
>> >               /* TODO: Add device role */ ?
>> >
>> >> +    }
>> >> +
>> >> +    if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>> >> +            dev_err(dev, "no supported roles\n");
>> >> +            return -ENODEV;
>> >> +    }
>> >> +
>> >> +    cdns->dr_mode = dr_mode;
>> >> +    return 0;
>> >> +}
>> >> +
>> >> +/**
>> >> + * cdns3_irq - interrupt handler for cdns3 core device
>> >> + *
>> >> + * @irq: irq number for cdns3 core device
>> >> + * @data: structure of cdns3
>> >> + *
>> >> + * Returns IRQ_HANDLED or IRQ_NONE
>> >> + */
>> >> +static irqreturn_t cdns3_irq(int irq, void *data)
>> >> +{
>> >> +    struct cdns3 *cdns = data;
>> >> +    irqreturn_t ret = IRQ_NONE;
>> >> +
>> >> +    /* Handle device/host interrupt */
>> >> +    if (cdns->role != CDNS3_ROLE_END)
>> >
>> >Is it because of this that you need to set role to END at role_stop?
>> >I think it is better to add a state variable to struct cdns3_role_driver, so we can
>> >check if it is active or stopped.
>> >
>> >e.g.
>> >       if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)
>>
>> Ok, I will do it in this way.
>> >> +            ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>> >> +
>> >> +    return ret;
>> >> +}
>> >> +
>> >> +static void cdns3_remove_roles(struct cdns3 *cdns)
>> >
>> >Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?
>>
>> Sounds better.
>> I also change cdns3_host_remove to cdns3_host_exit and
>> cdns3_gadget_remove to cdns3_gadget_exit.
>> >
>> >> +{
>> >> +    //TODO: implements this function
>> >> +}
>> >
>> >> +
>> >> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>> >> +{
>> >> +    enum cdns3_roles current_role;
>> >> +    int ret = 0;
>> >> +
>> >> +    current_role = cdns->role;
>> >> +
>> >> +    if (role == CDNS3_ROLE_END)
>> >> +            return 0;
>> >
>> >role == END looks like error state. and it should never happen.
>> >WARN here?
>>
>> Ok, will be changed.
>> >
>
>Please consider my comments which replied to Roger just now.

I  try to think also about you platform. Personally I think that someone from 
us should have access to both platforms in order to create one common driver.   
I use drd.c  file for detecting  ID pin. As I know  you use external connection class for
Detecting host/device role. 
My goals is not to create the common driver for our different platforms  because 
I don’t have access for your testing platform. My goals is to create driver that you 
could easily  adapt to your platform, so all your comment are very valuable for me. 
 
>
>> >> +
>> >> +    dev_dbg(cdns->dev, "Switching role");
>> >> +
>> >
>> >Don't you have to stop the previous role before starting the new role?
>> >
>> >> +    ret = cdns3_role_start(cdns, role);
>> >> +    if (ret) {
>> >> +            /* Back to current role */
>> >> +            dev_err(cdns->dev, "set %d has failed, back to %d\n",
>> >> +                    role, current_role);
>> >> +            ret = cdns3_role_start(cdns, current_role);
>> >> +    }
>> >> +
>> >> +    return ret;
>> >> +}
>> >> +
>> >> +/**
>> >> + * cdns3_role_switch - work queue handler for role switch
>> >> + *
>> >> + * @work: work queue item structure
>> >> + *
>> >> + * Handles below events:
>> >> + * - Role switch for dual-role devices
>> >> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
>> >> + */
>> >> +static void cdns3_role_switch(struct work_struct *work)
>> >> +{
>> >> +    enum cdns3_roles role = CDNS3_ROLE_END;
>> >> +    struct cdns3 *cdns;
>> >> +    bool device, host;
>> >> +
>> >> +    cdns = container_of(work, struct cdns3, role_switch_wq);
>> >> +
>> >> +    //TODO: implements this functions.
>> >> +    //host = cdns3_is_host(cdns);
>> >> +    //device = cdns3_is_device(cdns);
>> >> +    host = 1;
>> >> +    device = 0;
>> >> +
>> >> +    if (host)
>> >> +            role = CDNS3_ROLE_HOST;
>> >> +    else if (device)
>> >> +            role = CDNS3_ROLE_GADGET;
>> >> +
>> >> +    if (cdns->desired_dr_mode == cdns->current_dr_mode &&
>> >> +        cdns->role == role)
>> >> +            return;
>> >> +
>> >
>> >I think all the below code can be moved to cdns3_do_role_switch().
>>
>> Yes, I agree with you. cdns3_role_stop  should be in cdns3_do_role_switch.
>>
>> >> +    pm_runtime_get_sync(cdns->dev);
>> >> +    cdns3_role_stop(cdns);
>> >> +
>> >> +    if (host) {
>> >> +            if (cdns->roles[CDNS3_ROLE_HOST])
>> >> +                    cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>> >> +            pm_runtime_put_sync(cdns->dev);
>> >> +            return;
>> >> +    }
>> >> +
>> >> +    if (device)
>> >> +            cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
>> >> +    else
>> >> +            cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
>> >> +
>> >> +    pm_runtime_put_sync(cdns->dev);
>> >> +}
>> >> +
>> >> +/**
>> >> + * cdns3_probe - probe for cdns3 core device
>> >> + * @pdev: Pointer to cdns3 core platform device
>> >> + *
>> >> + * Returns 0 on success otherwise negative errno
>> >> + */
>> >> +static int cdns3_probe(struct platform_device *pdev)
>> >> +{
>> >> +    struct device *dev = &pdev->dev;
>> >> +    struct resource *res;
>> >> +    struct cdns3 *cdns;
>> >> +    void __iomem *regs;
>> >> +    int ret;
>> >> +
>> >> +    cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
>> >> +    if (!cdns)
>> >> +            return -ENOMEM;
>> >> +
>> >> +    cdns->dev = dev;
>> >> +
>> >> +    platform_set_drvdata(pdev, cdns);
>> >> +
>> >> +    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
>> >> +    if (!res) {
>> >> +            dev_err(dev, "missing IRQ\n");
>> >> +            return -ENODEV;
>> >> +    }
>> >> +    cdns->irq = res->start;
>> >> +
>> >> +    /*
>> >> +     * Request memory region
>> >> +     * region-0: xHCI
>> >> +     * region-1: Peripheral
>> >> +     * region-2: OTG registers
>> >> +     */
>> >
>> >The memory region order is different from the dt-binding.
>> >There it is OTG, host(xhci), device (peripheral).
>>
>> I corrected dt-binding.
>> >
>> >> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> >> +    regs = devm_ioremap_resource(dev, res);
>> >> +
>> >> +    if (IS_ERR(regs))
>> >> +            return PTR_ERR(regs);
>> >> +    cdns->xhci_regs = regs;
>> >> +    cdns->xhci_res = res;
>> >> +
>> >> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> >> +    regs = devm_ioremap_resource(dev, res);
>> >> +    if (IS_ERR(regs))
>> >> +            return PTR_ERR(regs);
>> >> +    cdns->dev_regs  = regs;
>> >> +
>> >> +    res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>> >> +    regs = devm_ioremap_resource(dev, res);
>> >> +    if (IS_ERR(regs))
>> >> +            return PTR_ERR(regs);
>> >> +    cdns->otg_regs = regs;
>> >> +
>> >> +    mutex_init(&cdns->mutex);
>> >> +
>> >> +    cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>> >
>> >"cdns3,usbphy" is not documented in dt-binding.
>>
>> I assume that I should add  to dt-binding (cdns3-usb.txt) something like:
>>  - phys: reference to the USB PHY
>>  - phy-names: name of the USB PHY, should be " cdns3,usbphy "
>>
>> >
>> >> +    if (IS_ERR(cdns->phy)) {
>> >> +            dev_info(dev, "no generic phy found\n");
>> >> +            cdns->phy = NULL;
>> >> +            /*
>> >> +             * fall through here!
>> >> +             * if no generic phy found, phy init
>> >> +             * should be done under boot!
>> >> +             */
>> >
>> >No you shouldn't fall through always if it is an error condition.
>> >Something like this should work better.
>> >
>> >        if (IS_ERR(cnds->phy)) {
>> >                ret = PTR_ERR(cdns->phy);
>> >                if (ret == -ENOSYS || ret == -ENODEV) {
>> >                        cdns->phy = NULL;
>> >                } else if (ret == -EPROBE_DEFER) {
>> >                        return ret;
>> >                } else {
>> >                        dev_err(dev, "no phy found\n");
>> >                        goto err0;
>> >                }
>> >        }
>> >
>> >So if PHY was provided in DT, and PHY support/drivers is present
>> >and error condition means something is wrong and we have to error out.
>> >
>> >> +    } else {
>> >> +            phy_init(cdns->phy);
>> >> +    }
>> >
>> >You can do phy_init() outside the else.
>> >
>> Thank you for explanation. I will correct this.
>> >> +
>> >> +    ret = cdns3_core_init_role(cdns);
>> >> +    if (ret)
>> >> +            goto err1;
>> >> +
>> >> +    INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
>> >> +    if (ret)
>> >> +            goto err2;
>> >> +
>> >> +    if (ret)
>> >> +            goto err2;
>> >> +
>> >> +    cdns->role = cdns3_get_role(cdns);
>> >
>> >I think this should move to cd I'll have a some though on ns3_core_init_role().
>>
>> Ok, I will do it.
>> >
>> >> +
>> >> +    ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
>> >> +                           dev_name(dev), cdns);
>> >> +
>> >> +    if (ret)
>> >> +            goto err2;
>> >
>> >How about moving request_irq to before cdsn3_core_init_role()?
>> >
>> >Then you can move cdns3_role_start() as well to core_init_role().
>> I'll give it  some though on it, but probably I will probably have to change little other function..
>> So the new order should look like this:
>>
>> cdns3_drd_init
>> devm_request_irq
>> cdns3_core_init_role, cdns3_get_role, cdns3_role_start
>>
>> >
>> >> +
>> >> +    ret = cdns3_role_start(cdns, cdns->role);
>> >> +    if (ret) {
>> >> +            dev_err(dev, "can't start %s role\n",
>> >> +                    cdns3_get_current_role_driver(cdns)->name);
>> >> +            goto err2;
>> >> +    }
>> >> +
>> >> +    device_set_wakeup_capable(dev, true);
>> >> +    pm_runtime_set_active(dev);
>> >> +    pm_runtime_enable(dev);
>> >> +
>> >> +    /*
>> >> +     * The controller needs less time between bus and controller suspend,
>> >> +     * and we also needs a small delay to avoid frequently entering low
>> >> +     * power mode.
>> >> +     */
>> >> +    pm_runtime_set_autosuspend_delay(dev, 20);
>> >> +    pm_runtime_mark_last_busy(dev);
>> >> +    pm_runtime_use_autosuspend(dev);
>> >> +    dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
>> >> +
>> >> +    return 0;
>> >> +
>> >> +err2:
>> >> +    cdns3_remove_roles(cdns);
>> >> +err1:
>> >
>> >phy_exit() ?
>> I will add.
>> >
>> >> +    return ret;
>> >> +}
>> >> +
>> >> +/**
>> >> + * cdns3_remove - unbind drd driver and clean up
>> >> + * @pdev: Pointer to Linux platform device
>> >> + *
>> >> + * Returns 0 on success otherwise negative errno
>> >> + */
>> >> +static int cdns3_remove(struct platform_device *pdev)
>> >> +{
>> >> +    struct cdns3 *cdns = platform_get_drvdata(pdev);
>> >> +
>> >> +    pm_runtime_get_sync(&pdev->dev);
>> >> +    pm_runtime_disable(&pdev->dev);
>> >> +    pm_runtime_put_noidle(&pdev->dev);
>> >> +    cdns3_remove_roles(cdns);
>> >
>> >phy_exit() ?
>> I will add.
>> >
>> >> +
>> >> +    return 0;
>> >> +}
>> >> +
>> >> +#ifdef CONFIG_OF
>> >> +static const struct of_device_id of_cdns3_match[] = {
>> >> +    { .compatible = "cdns,usb3" },
>> >> +    { },
>> >> +};
>> >> +MODULE_DEVICE_TABLE(of, of_cdns3_match);
>> >> +#endif
>> >> +
>> >> +#ifdef CONFIG_PM
>> >> +
>> >> +#ifdef CONFIG_PM_SLEEP
>> >> +static int cdns3_suspend(struct device *dev)
>> >> +{
>> >> +    //TODO: implements this function
>> >> +    return 0;
>> >> +}
>> >> +
>> >> +static int cdns3_resume(struct device *dev)
>> >> +{
>> >> +    //TODO: implements this function
>> >> +    return 0;
>> >> +}
>> >> +#endif /* CONFIG_PM_SLEEP */
>> >> +static int cdns3_runtime_suspend(struct device *dev)
>> >> +{   //TODO: implements this function
>> >> +    return 0;
>> >> +}
>> >> +
>> >> +static int cdns3_runtime_resume(struct device *dev)
>> >> +{
>> >> +    //TODO: implements this function
>> >> +    return 0;
>> >> +}
>> >> +#endif /* CONFIG_PM */
>> >> +
>> >> +static const struct dev_pm_ops cdns3_pm_ops = {
>> >> +    SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
>> >> +    SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
>> >> +};
>> >> +
>> >> +static struct platform_driver cdns3_driver = {
>> >> +    .probe          = cdns3_probe,
>> >> +    .remove         = cdns3_remove,
>> >> +    .driver         = {
>> >> +            .name   = "cdns-usb3",
>> >> +            .of_match_table = of_match_ptr(of_cdns3_match),
>> >> +            .pm     = &cdns3_pm_ops,
>> >> +    },
>> >> +};
>> >> +
>> >> +static int __init cdns3_driver_platform_register(void)
>> >> +{
>> >> +    return platform_driver_register(&cdns3_driver);
>> >> +}
>> >> +module_init(cdns3_driver_platform_register);
>> >> +
>> >> +static void __exit cdns3_driver_platform_unregister(void)
>> >> +{
>> >> +    platform_driver_unregister(&cdns3_driver);
>> >> +}
>> >> +module_exit(cdns3_driver_platform_unregister);
>> >> +
>> >> +MODULE_ALIAS("platform:cdns3");
>> >> +MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
>> >> +MODULE_LICENSE("GPL v2");
>> >> +MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
>> >> diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
>> >> new file mode 100644
>> >> index 000000000000..7c8204fe4d3d
>> >> --- /dev/null
>> >> +++ b/drivers/usb/cdns3/core.h
>> >> @@ -0,0 +1,100 @@
>> >> +/* SPDX-License-Identifier: GPL-2.0 */
>> >> +/*
>> >> + * Cadence USBSS DRD Driver.
>> >> + *
>> >> + * Copyright (C) 2017 NXP
>> >> + * Copyright (C) 2018 Cadence.
>> >> + *
>> >> + * Authors: Peter Chen <peter.chen@nxp.com>
>> >> + *          Pawel Laszczak <pawell@cadence.com>
>> >> + */
>> >> +#include <linux/usb/otg.h>
>> >> +
>> >> +#ifndef __LINUX_CDNS3_CORE_H
>> >> +#define __LINUX_CDNS3_CORE_H
>> >> +
>> >> +struct cdns3;
>> >> +enum cdns3_roles {
>> >> +    CDNS3_ROLE_HOST = 0,
>> >> +    CDNS3_ROLE_GADGET,
>> >> +    CDNS3_ROLE_END,
>> >> +};
>> >> +
>> >> +/**
>> >> + * struct cdns3_role_driver - host/gadget role driver
>> >> + * @start: start this role
>> >> + * @stop: stop this role
>> >> + * @suspend: suspend callback for this role
>> >> + * @resume: resume callback for this role
>> >> + * @irq: irq handler for this role
>> >> + * @name: role name string (host/gadget)
>> >> + */
>> >> +struct cdns3_role_driver {
>> >> +    int (*start)(struct cdns3 *cdns);
>> >> +    void (*stop)(struct cdns3 *cdns);
>> >> +    int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
>> >> +    int (*resume)(struct cdns3 *cdns, bool hibernated);
>> >> +    irqreturn_t (*irq)(struct cdns3 *cdns);
>> >> +    const char *name;
>> >> +};
>> >> +
>> >> +#define CDNS3_NUM_OF_CLKS   5
>> >> +/**
>> >> + * struct cdns3 - Representation of Cadence USB3 DRD controller.
>> >> + * @dev: pointer to Cadence device struct
>> >> + * @xhci_regs: pointer to base of xhci registers
>> >> + * @xhci_res: the resource for xhci
>> >> + * @dev_regs: pointer to base of dev registers
>> >> + * @otg_regs: pointer to base of otg registers
>> >> + * @irq: irq number for controller
>> >> + * @roles: array of supported roles for this controller
>> >> + * @role: current role
>> >> + * @host_dev: the child host device pointer for cdns3 core
>> >> + * @gadget_dev: the child gadget device pointer for cdns3 core
>> >> + * @usb: phy for this controller
>> >> + * @role_switch_wq: work queue item for role switch
>> >> + * @in_lpm: the controller in low power mode
>> >> + * @wakeup_int: the wakeup interrupt
>> >> + * @mutex: the mutex for concurrent code at driver
>> >> + * @dr_mode: supported mode of operation it can be only Host, only Device
>> >> + *           or OTG mode that allow to switch between Device and Host mode.
>> >> + *           This field based on hardware configuration and cant't be changed.
>> >
>> >But dr_mode can be forced in device-tree. So it isn't really only hardware configuration.
>> Right, I added dr_mode to dt-binding , so we have STRAP bits in registers and additionally
>> optional dr_mode in device-tree. Driver should take into account this two options.
>> I will remove this line.
>>
>> >
>> >> + * @current_dr_role: current mode of operation when in dual-role mode
>> >> + * @desired_dr_role: desired mode of operation when in dual-role mode.
>> >> + *           This value can be changed during runtime.
>> >> + *           Available options depends on  dr_mode:
>> >> + *           dr_mode                 |  desired_dr_role and current_dr_role
>> >> + *           ----------------------------------------------------------------
>> >> + *           USB_DR_MODE_HOST        | only USB_DR_MODE_HOST
>> >> + *           USB_DR_MODE_PERIPHERAL  | only USB_DR_MODE_PERIPHERAL
>> >> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_HOST
>> >> + *           USB_DR_MODE_OTG         | only USB_DR_MODE_PERIPHERAL
>> >> + *           USB_DR_MODE_OTG         | USB_DR_MODE_OTG
>> >
>> >Do you need to update the right hand side to reflect ROLEs instead of MODE?
>>
>> I see that there are incorrect name. There should be mode instead role.
>> In structure below the names are correct.
>> >
>
>Usually, we have two type of roles: init role and current role
>init role is decided by dr_mode from firmware, hardware setting and
>kernel configuration together.
>It is decided at ->probe, if above three settings haveconflict, we
>need show an error.
>current role is decided at run time, and only used at dual-role mode.
>For peripheral-only and host-only application, the current role equals
>to init role.
>

I agree. 

But by conflict I understand that we can't chose anyone option. 

e.g.  
Firmware:   OTG
Hardware :  only device
Kernel :   only host 
We can't choose the option. 

But in case 
Firmware:   OTG
Hardware :  only device
Kernel :   only device 
Then we can set dr_mode to HOST device mode and init role will be Host role.


>Peter
>
>> >> + *
>> >> + *           Desired_dr_role can be changed by means of debugfs.
>> >> + * @root: debugfs root folder pointer
>> >> + */
>> >> +struct cdns3 {
>> >> +    struct device                   *dev;
>> >> +    void __iomem                    *xhci_regs;
>> >> +    struct resource                 *xhci_res;
>> >> +    struct cdns3_usb_regs __iomem   *dev_regs;
>> >> +    struct cdns3_otg_regs           *otg_regs;
>> >> +    int irq;
>> >> +    struct cdns3_role_driver        *roles[CDNS3_ROLE_END];
>> >> +    enum cdns3_roles                role;
>> >> +    struct device                   *host_dev;
>> >> +    struct device                   *gadget_dev;
>> >> +    struct phy                      *phy;
>> >> +    struct work_struct              role_switch_wq;
>> >> +    int                             in_lpm:1;
>> >> +    int                             wakeup_int:1;
>> >> +    /* mutext used in workqueue*/
>> >> +    struct mutex                    mutex;
>> >> +    enum usb_dr_mode                dr_mode;
>> >> +    enum usb_dr_mode                current_dr_mode;
>> >> +    enum usb_dr_mode                desired_dr_mode;
>> >> +    struct dentry                   *root;
>> >> +};
>> >> +
>> >> +#endif /* __LINUX_CDNS3_CORE_H */
>> >>
>> >
>> >cheers,
>> >-roger
>> >--
>> >Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
>> >Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki
>>
>> Thank for all your comments,
>> Cheers,
>> Pawel

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

* RE: [RFC PATCH v2 05/15] usb:cdns3: Added DRD support
  2018-12-04  9:18   ` Peter Chen
@ 2018-12-06  7:25     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-06  7:25 UTC (permalink / raw)
  To: Peter Chen
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, rogerq, lkml,
	Alan Douglas, jbergsagel, nsekhar, nm, Suresh Punnoose,
	peter.chen, Pawel Jez, Rahul Kumar

>> Patch adds supports for detecting Host/Device mode.
>> Controller has additional OTG register that allow
>> implement even whole OTG functionality.
>> At this moment patch adds support only for detecting
>> the appropriate mode based on strap pins and ID pin.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  drivers/usb/cdns3/Makefile |   2 +-
>>  drivers/usb/cdns3/core.c   |  27 +++--
>>  drivers/usb/cdns3/drd.c    | 229 +++++++++++++++++++++++++++++++++++++
>>  drivers/usb/cdns3/drd.h    | 122 ++++++++++++++++++++
>>  4 files changed, 372 insertions(+), 8 deletions(-)
>>  create mode 100644 drivers/usb/cdns3/drd.c
>>  create mode 100644 drivers/usb/cdns3/drd.h
>>
>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile
>> index 02d25b23c5d3..e779b2a2f8eb 100644
>> --- a/drivers/usb/cdns3/Makefile
>> +++ b/drivers/usb/cdns3/Makefile
>> @@ -1,5 +1,5 @@
>>  obj-$(CONFIG_USB_CDNS3)                        += cdns3.o
>>  obj-$(CONFIG_USB_CDNS3_PCI_WRAP)       += cdns3-pci.o
>>
>> -cdns3-y                                        := core.o
>> +cdns3-y                                        := core.o drd.o
>>  cdns3-pci-y                            := cdns3-pci-wrap.o
>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
>> index f9055d4da67f..dbee4325da7f 100644
>> --- a/drivers/usb/cdns3/core.c
>> +++ b/drivers/usb/cdns3/core.c
>> @@ -17,6 +17,7 @@
>>
>>  #include "gadget.h"
>>  #include "core.h"
>> +#include "drd.h"
>>
>>  static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>  {
>> @@ -57,8 +58,10 @@ static inline void cdns3_role_stop(struct cdns3 *cdns)
>>  static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>  {
>>         if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>> -               //TODO: implements selecting device/host mode
>> -               return CDNS3_ROLE_HOST;
>> +               if (cdns3_is_host(cdns))
>> +                       return CDNS3_ROLE_HOST;
>> +               if (cdns3_is_device(cdns))
>> +                       return CDNS3_ROLE_GADGET;
>>         }
>>         return cdns->roles[CDNS3_ROLE_HOST]
>>                 ? CDNS3_ROLE_HOST
>> @@ -124,6 +127,12 @@ static irqreturn_t cdns3_irq(int irq, void *data)
>>         struct cdns3 *cdns = data;
>>         irqreturn_t ret = IRQ_NONE;
>>
>> +       if (cdns->dr_mode == USB_DR_MODE_OTG) {
>> +               ret = cdns3_drd_irq(cdns);
>> +               if (ret == IRQ_HANDLED)
>> +                       return ret;
>> +       }
>> +
>>         /* Handle device/host interrupt */
>>         if (cdns->role != CDNS3_ROLE_END)
>>                 ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>> @@ -176,11 +185,8 @@ static void cdns3_role_switch(struct work_struct *work)
>>
>>         cdns = container_of(work, struct cdns3, role_switch_wq);
>>
>> -       //TODO: implements this functions.
>> -       //host = cdns3_is_host(cdns);
>> -       //device = cdns3_is_device(cdns);
>> -       host = 1;
>> -       device = 0;
>> +       host = cdns3_is_host(cdns);
>> +       device = cdns3_is_device(cdns);
>>
>>         if (host)
>>                 role = CDNS3_ROLE_HOST;
>> @@ -194,6 +200,12 @@ static void cdns3_role_switch(struct work_struct *work)
>>         pm_runtime_get_sync(cdns->dev);
>>         cdns3_role_stop(cdns);
>>
>> +       if (cdns->desired_dr_mode != cdns->current_dr_mode) {
>> +               cdns3_drd_update_mode(cdns);
>> +               host = cdns3_is_host(cdns);
>> +               device = cdns3_is_device(cdns);
>> +       }
>> +
>>         if (host) {
>>                 if (cdns->roles[CDNS3_ROLE_HOST])
>>                         cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>> @@ -287,6 +299,7 @@ static int cdns3_probe(struct platform_device *pdev)
>>         if (ret)
>>                 goto err2;
>>
>> +       ret = cdns3_drd_init(cdns);
>>         if (ret)
>>                 goto err2;
>>
>> diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
>> new file mode 100644
>> index 000000000000..ac741c80e776
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/drd.c
>> @@ -0,0 +1,229 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Cadence USBSS DRD Driver.
>> + *
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Author: Pawel Laszczak <pawell@cadence.com
>> + *
>> + */
>> +#include <linux/kernel.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/usb/otg.h>
>> +
>> +#include "gadget.h"
>> +#include "drd.h"
>> +
>> +/**
>> + * cdns3_set_mode - change mode of OTG Core
>> + * @cdns: pointer to context structure
>> + * @mode: selected mode from cdns_role
>> + */
>> +void cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
>> +{
>> +       u32 reg;
>> +
>> +       cdns->current_dr_mode = mode;
>> +       switch (mode) {
>> +       case USB_DR_MODE_PERIPHERAL:
>> +               dev_info(cdns->dev, "Set controller to Gadget mode\n");
>> +               writel(OTGCMD_DEV_BUS_REQ | OTGCMD_OTG_DIS,
>> +                      &cdns->otg_regs->cmd);
>> +               break;
>> +       case USB_DR_MODE_HOST:
>> +               dev_info(cdns->dev, "Set controller to Host mode\n");
>> +               writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
>> +                      &cdns->otg_regs->cmd);
>> +               break;
>> +       case USB_DR_MODE_OTG:
>> +               dev_info(cdns->dev, "Set controller to OTG mode\n");
>> +               reg = readl(&cdns->otg_regs->ctrl1);
>> +               reg |= OTGCTRL1_IDPULLUP;
>> +               writel(reg, &cdns->otg_regs->ctrl1);
>> +
>> +               /* wait until valid ID (ID_VALUE) can be sampled (50ms). */
>> +               mdelay(50);
>
>Usually, each big delay needs well documentation, eg, from hardware's
>documentation.
>And use sleep delay, eg, msleep or usleep_range.

Ok, 

>
>> +               break;
>> +       default:
>> +               cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
>> +               dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
>> +               return;
>> +       }
>> +}
>> +
>> +static int cdns3_otg_get_id(struct cdns3 *cdns)
>> +{
>> +       int id;
>> +
>> +       id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
>> +       dev_dbg(cdns->dev, "OTG ID: %d", id);
>> +       return id;
>> +}
>> +
>> +int cdns3_is_host(struct cdns3 *cdns)
>> +{
>> +       if (cdns->current_dr_mode == USB_DR_MODE_HOST)
>> +               return 1;
>> +       else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>> +               if (!cdns3_otg_get_id(cdns))
>> +                       return 1;
>> +
>> +       return 0;
>> +}
>> +
>> +int cdns3_is_device(struct cdns3 *cdns)
>> +{
>> +       if (cdns->current_dr_mode == USB_DR_MODE_PERIPHERAL)
>> +               return 1;
>> +       else if (cdns->current_dr_mode == USB_DR_MODE_OTG)
>> +               if (cdns3_otg_get_id(cdns))
>> +                       return 1;
>> +
>> +       return 0;
>> +}
>> +
>
>You could move above into cdns_get_role.

But these two function are also used in cdns3_get_initial_role in core.c file.
>
>> +/**
>> + * cdns3_otg_disable_irq - Disable all OTG interrupts
>> + * @cdns: Pointer to controller context structure
>> + */
>> +static void cdns3_otg_disable_irq(struct cdns3 *cdns)
>> +{
>> +       writel(0, &cdns->otg_regs->ien);
>> +}
>> +
>> +/**
>> + * cdns3_otg_enable_irq - enable id and sess_valid interrupts
>> + * @cdns: Pointer to controller context structure
>> + */
>> +static void cdns3_otg_enable_irq(struct cdns3 *cdns)
>> +{
>> +       writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
>> +              OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien);
>> +}
>> +
>> +/**
>> + * cdns3_init_otg_mode - initialize drd controller
>> + * @cdns: Pointer to controller context structure
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +static void cdns3_init_otg_mode(struct cdns3 *cdns)
>> +{
>> +       cdns3_otg_disable_irq(cdns);
>> +       /* clear all interrupts */
>> +       writel(~0, &cdns->otg_regs->ivect);
>> +
>> +       cdns3_set_mode(cdns, USB_DR_MODE_OTG);
>> +
>> +       cdns3_otg_enable_irq(cdns);
>> +}
>> +
>> +/**
>> + * cdns3_drd_update_mode - initialize mode of operation
>> + * @cdns: Pointer to controller context structure
>> + *
>> + * Returns 0 on success otherwise negative errno
>> + */
>> +int cdns3_drd_update_mode(struct cdns3 *cdns)
>> +{
>> +       int ret = 0;
>> +
>> +       switch (cdns->desired_dr_mode) {
>> +       case USB_DR_MODE_PERIPHERAL:
>> +               cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
>> +               break;
>> +       case USB_DR_MODE_HOST:
>> +               cdns3_set_mode(cdns, USB_DR_MODE_HOST);
>> +               break;
>> +       case USB_DR_MODE_OTG:
>> +               cdns3_init_otg_mode(cdns);
>> +               break;
>> +       default:
>> +               dev_err(cdns->dev, "Unsupported mode of operation %d\n",
>> +                       cdns->dr_mode);
>> +               return -EINVAL;
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns)
>> +{
>> +       irqreturn_t ret = IRQ_NONE;
>> +       u32 reg;
>> +
>> +       if (cdns->dr_mode != USB_DR_MODE_OTG)
>> +               return ret;
>> +
>> +       reg = readl(&cdns->otg_regs->ivect);
>> +       if (!reg)
>> +               return ret;
>> +
>> +       if (reg & OTGIEN_ID_CHANGE_INT) {
>> +               int id = cdns3_otg_get_id(cdns);
>> +
>> +               dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
>> +                       cdns3_otg_get_id(cdns));
>> +
>> +               if (id)
>> +                       cdns->role = CDNS3_ROLE_GADGET;
>> +               else
>> +                       cdns->role = CDNS3_ROLE_HOST;
>> +
>> +               queue_work(system_freezable_wq, &cdns->role_switch_wq);
>> +
>> +               ret = IRQ_HANDLED;
>> +       }
>> +
>> +       writel(~0, &cdns->otg_regs->ivect);
>> +       return IRQ_HANDLED;
>> +}
>> +
>> +int cdns3_drd_init(struct cdns3 *cdns)
>> +{
>> +       enum usb_dr_mode dr_mode;
>> +       int ret = 0;
>> +       u32 state;
>> +
>> +       state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts));
>> +
>> +       dr_mode = cdns->dr_mode;
>> +       if (state == OTGSTS_STRAP_HOST) {
>> +               dev_info(cdns->dev, "Controller strapped to HOST\n");
>> +               dr_mode = USB_DR_MODE_HOST;
>> +               if (cdns->dr_mode != USB_DR_MODE_HOST &&
>> +                   cdns->dr_mode != USB_DR_MODE_OTG)
>> +                       ret = -EINVAL;
>> +       } else if (state == OTGSTS_STRAP_GADGET) {
>> +               dev_info(cdns->dev, "Controller strapped to PERIPHERAL\n");
>> +               dr_mode = USB_DR_MODE_PERIPHERAL;
>> +               if (cdns->dr_mode != USB_DR_MODE_PERIPHERAL &&
>> +                   cdns->dr_mode != USB_DR_MODE_OTG)
>> +                       ret = -EINVAL;
>> +       }
>> +
>> +       if (ret) {
>> +               dev_err(cdns->dev, "Incorrect DRD configuration\n");
>> +               return ret;
>> +       }
>> +
>> +       //Updating DR mode according to strap.
>> +       cdns->dr_mode = dr_mode;
>> +       cdns->desired_dr_mode = dr_mode;
>> +       cdns->current_dr_mode = USB_DR_MODE_UNKNOWN;
>> +
>> +       dev_info(cdns->dev, "Controller Device ID: %08lx, Revision ID: %08lx\n",
>> +                CDNS_RID(readl(&cdns->otg_regs->rid)),
>> +                CDNS_DID(readl(&cdns->otg_regs->did)));
>> +
>> +       state = readl(&cdns->otg_regs->sts);
>> +       if (OTGSTS_OTG_NRDY(state) != 0) {
>> +               dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
>> +               return -ENODEV;
>> +       }
>> +
>> +       ret = cdns3_drd_update_mode(cdns);
>> +
>> +       return ret;
>> +}
>> diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h
>> new file mode 100644
>> index 000000000000..0faa7520ecac
>> --- /dev/null
>> +++ b/drivers/usb/cdns3/drd.h
>> @@ -0,0 +1,122 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Cadence USB3 DRD part of USBSS driver
>
>Header file
>
>Peter
>
>> + *
>> + * Copyright (C) 2018 Cadence.
>> + *
>> + * Author: Pawel Laszczak <pawell@cadence.com>
>> + */
>> +#ifndef __LINUX_CDNS3_DRD
>> +#define __LINUX_CDNS3_DRD
>> +
>> +#include <linux/usb/otg.h>
>> +#include <linux/phy/phy.h>
>> +#include "core.h"
>> +
>> +/*  DRD register interface. */
>> +struct cdns3_otg_regs {
>> +       __le32 did;
>> +       __le32 rid;
>> +       __le32 capabilities;
>> +       __le32 reserved1;
>> +       __le32 cmd;
>> +       __le32 sts;
>> +       __le32 state;
>> +       __le32 reserved2;
>> +       __le32 ien;
>> +       __le32 ivect;
>> +       __le32 refclk;
>> +       __le32 tmr;
>> +       __le32 reserved3[4];
>> +       __le32 simulate;
>> +       __le32 override;
>> +       __le32 susp_ctrl;
>> +       __le32 reserved4;
>> +       __le32 anasts;
>> +       __le32 adp_ramp_time;
>> +       __le32 ctrl1;
>> +       __le32 ctrl2;
>> +};
>> +
>> +/* CDNS_RID - bitmasks */
>> +#define CDNS_RID(p)                    ((p) & GENMASK(15, 0))
>> +
>> +/* CDNS_VID - bitmasks */
>> +#define CDNS_DID(p)                    ((p) & GENMASK(31, 0))
>> +
>> +/* OTGCMD - bitmasks */
>> +/* "Request the bus for Device mode. */
>> +#define OTGCMD_DEV_BUS_REQ     BIT(0)
>> +/* Request the bus for Host mode */
>> +#define OTGCMD_HOST_BUS_REQ            BIT(1)
>> +/* Enable OTG mode. */
>> +#define OTGCMD_OTG_EN                  BIT(2)
>> +/* Disable OTG mode */
>> +#define OTGCMD_OTG_DIS                 BIT(3)
>> +/*"Configure OTG as A-Device. */
>> +#define OTGCMD_A_DEV_EN                        BIT(4)
>> +/*"Configure OTG as A-Device. */
>> +#define OTGCMD_A_DEV_DIS               BIT(5)
>> +/* Drop the bus for Device mod e. */
>> +#define OTGCMD_DEV_BUS_DROP            BIT(8)
>> +/* Drop the bus for Host mode*/
>> +#define OTGCMD_HOST_BUS_DROP           BIT(9)
>> +/* Power Down USBSS-DEV. */
>> +#define OTGCMD_DEV_POWER_OFF           BIT(11)
>> +/* Power Down CDNSXHCI. */
>> +#define OTGCMD_HOST_POWER_OFF          BIT(12)
>> +
>> +/* OTGIEN - bitmasks */
>> +/* ID change interrupt enable */
>> +#define OTGIEN_ID_CHANGE_INT           BIT(0)
>> +/* Vbusvalid fall detected interrupt enable.*/
>> +#define OTGIEN_VBUSVALID_RISE_INT      BIT(4)
>> +/* Vbusvalid fall detected interrupt enable */
>> +#define OTGIEN_VBUSVALID_FALL_INT      BIT(5)
>> +
>> +/* OTGSTS - bitmasks */
>> +/*
>> + * Current value of the ID pin. It is only valid when idpullup in
>> + *  OTGCTRL1_TYPE register is set to '1'.
>> + */
>> +#define OTGSTS_ID_VALUE                        BIT(0)
>> +/* Current value of the vbus_valid */
>> +#define OTGSTS_VBUS_VALID              BIT(1)
>> +/* Current value of the b_sess_vld */
>> +#define OTGSTS_SESSION_VALID           BIT(2)
>> +/*Device mode is active*/
>> +#define OTGSTS_DEV_ACTIVE              BIT(3)
>> +/* Host mode is active. */
>> +#define OTGSTS_HOST_ACTIVE             BIT(4)
>> +/* OTG Controller not ready. */
>> +#define OTGSTS_OTG_NRDY_MASK           BIT(11)
>> +#define OTGSTS_OTG_NRDY(p)             ((p) & OTGSTS_OTG_NRDY_MASK)
>> +/*
>> + * Value of the strap pins.
>> + * 000 - no default configuration
>> + * 010 - Controller initiall configured as Host
>> + * 100 - Controller initially configured as Device
>> + */
>> +#define OTGSTS_STRAP(p)                        (((p) & GENMASK(14, 12)) >> 12)
>> +#define OTGSTS_STRAP_NO_DEFAULT_CFG    0x00
>> +#define OTGSTS_STRAP_HOST_OTG          0x01
>> +#define OTGSTS_STRAP_HOST              0x02
>> +#define OTGSTS_STRAP_GADGET            0x04
>> +/* Host mode is turned on. */
>> +#define OTGSTSE_XHCI_READYF            BIT(26)
>> +/* "Device mode is turned on .*/
>> +#define OTGSTS_DEV_READY               BIT(27)
>> +
>> +/* OTGREFCLK - bitmasks */
>> +#define OTGREFCLK_STB_CLK_SWITCH_EN    BIT(31)
>> +
>> +/* OTGCTRL1 - bitmasks */
>> +#define OTGCTRL1_IDPULLUP              BIT(24)
>> +
>> +int cdns3_is_host(struct cdns3 *cdns);
>> +int cdns3_is_device(struct cdns3 *cdns);
>> +int cdns3_drd_init(struct cdns3 *cdns);
>> +int cdns3_drd_update_mode(struct cdns3 *cdns);
>> +irqreturn_t cdns3_drd_irq(struct cdns3 *cdns);
>> +
>> +#endif /* __LINUX_CDNS3_DRD */
>> --
>> 2.17.1
>>

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-04 10:46       ` Roger Quadros
  2018-12-05  8:57         ` Peter Chen
@ 2018-12-06  9:31         ` Pawel Laszczak
  1 sibling, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-06  9:31 UTC (permalink / raw)
  To: Roger Quadros, Peter Chen
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, lkml, Alan Douglas,
	jbergsagel, nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar

Hi,

>On 04/12/18 10:50, Peter Chen wrote:
>>>> + * Cadence USBSS DRD Driver.
>>>> + *
>>>> + * Copyright (C) 2018 Cadence.
>>>> + *
>>>> + * Author: Peter Chen <peter.chen@nxp.com>
>>>> + *         Pawel Laszczak <pawell@cadence.com>
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/kernel.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +
>>>> +#include "gadget.h"
>>>> +#include "core.h"
>>>> +
>>>> +static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
>>>> +{
>>>> +     WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]);
>>>> +     return cdns->roles[cdns->role];
>>>> +}
>>>> +
>>>> +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role)
>>>> +{
>>>> +     int ret;
>>>> +
>>>> +     if (role >= CDNS3_ROLE_END)
>>>
>>> WARN_ON()?
>>>
>>>> +             return 0;
>>>> +
>>>> +     if (!cdns->roles[role])
>>>> +             return -ENXIO;
>>>> +
>>>> +     mutex_lock(&cdns->mutex);
>>>> +     cdns->role = role;
>>>> +     ret = cdns->roles[role]->start(cdns);
>>>> +     mutex_unlock(&cdns->mutex);
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +static inline void cdns3_role_stop(struct cdns3 *cdns)
>>>> +{
>>>> +     enum cdns3_roles role = cdns->role;
>>>> +
>>>> +     if (role == CDNS3_ROLE_END)
>>>
>>> WARN_ON(role >= CNDS3_ROLE_END) ?
>>>
>>>> +             return;
>>>> +
>>>> +     mutex_lock(&cdns->mutex);
>>>> +     cdns->roles[role]->stop(cdns);
>>>> +     cdns->role = CDNS3_ROLE_END;
>>>
>>> Why change the role here? You are just stopping the role not changing it.
>>> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>>> if required without error.
>>>
>>
>> The current version of this IP has some issues to detect vbus status correctly,
>> we have to force vbus status accordingly, so we need a status to indicate
>> vbus disconnection, and add some code to let controller know vbus
>> removal, in that case, the controller's state machine can be correct.
>> So, we increase one role 'CDNS3_ROLE_END' to for this purpose.
>>
>> CDNS3_ROLE_GADGET: gadget mode and VBUS on
>> CDNS3_ROLE_HOST: host mode and VBUS on
>> CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.
>>
>> So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
>> and need to set role as CDNS3_ROLE_END at ->stop for further handling at
>> role switch routine.
>
>OK. but still this (changing to ROLE_END) must be moved to the role switch routine
>and the explanation you just mentioned must be added there.
>
>>
>>>> +     mutex_unlock(&cdns->mutex);
>>>> +}
>>>> +
>>>> +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>>> +{
>>>> +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>>> +             //TODO: implements selecting device/host mode
>>>> +             return CDNS3_ROLE_HOST;
>>>> +     }
>>>> +     return cdns->roles[CDNS3_ROLE_HOST]
>>>> +             ? CDNS3_ROLE_HOST
>>>> +             : CDNS3_ROLE_GADGET;
>>>
>>> Why not just
>>>         return cdns->role;
>>>
>>> I'm wondering if we really need this function.
>>
>> cdns->role gets from cdns3_get_role, and this API tells role at the runtime.
>> If both roles are supported, the role is decided by external
>> conditions, eg, vbus/id
>> or external connector. If only single role is supported, only one role structure
>> is allocated, cdns->roles[CDNS3_ROLE_HOST] or cdns->roles[CDNS3_ROLE_GADGET]
>>
>
>How about adding this description in function documentation.

Ok, I will do it. 
>
>>>> +}
>>>
>>>> +
>>>> +/**
>>>> + * cdns3_core_init_role - initialize role of operation
>>>> + * @cdns: Pointer to cdns3 structure
>>>> + *
>>>> + * Returns 0 on success otherwise negative errno
>>>> + */
>>>> +static int cdns3_core_init_role(struct cdns3 *cdns)
>>>> +{
>>>> +     struct device *dev = cdns->dev;
>>>> +     enum usb_dr_mode dr_mode;
>>>> +
>>>> +     dr_mode = usb_get_dr_mode(dev);
>>>> +     cdns->role = CDNS3_ROLE_END;
>>>> +
>>>> +     /*
>>>> +      * If driver can't read mode by means of usb_get_dr_mdoe function then
>>>> +      * chooses mode according with Kernel configuration. This setting
>>>> +      * can be restricted later depending on strap pin configuration.
>>>> +      */
>>>> +     if (dr_mode == USB_DR_MODE_UNKNOWN) {
>>>> +             if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
>>>> +                 IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>>>> +                     dr_mode = USB_DR_MODE_OTG;
>>>> +             else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
>>>> +                     dr_mode = USB_DR_MODE_HOST;
>>>> +             else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
>>>> +                     dr_mode = USB_DR_MODE_PERIPHERAL;
>>>> +     }
>>>> +
>>>> +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
>>>> +             //TODO: implements host initialization
>>>
>>>                 /* TODO: Add host role */ ?
>>>
>>>> +     }
>>>> +
>>>> +     if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
>>>> +             //TODO: implements device initialization
>>>
>>>                 /* TODO: Add device role */ ?
>>>
>>
>> Yes, it needs to allocate cdns->roles[CDNS3_ROLE_HOST] and
>> cdns->roles[CDNS3_ROLE_GADGET].
>>
>>>> +     }
>>>> +
>>>> +     if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
>>>> +             dev_err(dev, "no supported roles\n");
>>>> +             return -ENODEV;
>>>> +     }
>>>> +
>>>> +     cdns->dr_mode = dr_mode;
>>
>> Pawel, why dr_mode needs to be introduced?
>>
>>>> +     return 0;
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_irq - interrupt handler for cdns3 core device
>>>> + *
>>>> + * @irq: irq number for cdns3 core device
>>>> + * @data: structure of cdns3
>>>> + *
>>>> + * Returns IRQ_HANDLED or IRQ_NONE
>>>> + */
>>>> +static irqreturn_t cdns3_irq(int irq, void *data)
>>>> +{
>>>> +     struct cdns3 *cdns = data;
>>>> +     irqreturn_t ret = IRQ_NONE;
>>>> +
>>>> +     /* Handle device/host interrupt */
>>>> +     if (cdns->role != CDNS3_ROLE_END)
>>>
>>> Is it because of this that you need to set role to END at role_stop?
>>> I think it is better to add a state variable to struct cdns3_role_driver, so we can
>>> check if it is active or stopped.
>>>
>>> e.g.
>>>         if (cdns3_get_current_role_driver(cdns)->state == CDNS3_ROLE_STATE_ACTIVE)
>>>
>>>> +             ret = cdns3_get_current_role_driver(cdns)->irq(cdns);
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>
>>  CDNS3_ROLE_END is introduced from above comments, we don't
>> need another flag for it.
>> If cdns->role == CDNS3_ROLE_END, it handles VBUS and ID interrupt.
>>
>>>> +static void cdns3_remove_roles(struct cdns3 *cdns)
>>>
>>> Should this be called cdns3_exit_roles() to be opposite of cdns3_init_roles()?
>>>
>>
>> It is planed to called when at ->remove.
>>>> +{
>>>> +     //TODO: implements this function
>>>> +}
>>>
>>>> +
>>>> +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
>>>> +{
>>>> +     enum cdns3_roles current_role;
>>>> +     int ret = 0;
>>>> +
>>>> +     current_role = cdns->role;
>>>> +
>>>> +     if (role == CDNS3_ROLE_END)
>>>> +             return 0;
>>>
>>> role == END looks like error state. and it should never happen.
>>> WARN here?
>>>
>>
>> See my comments above.
>>
>>>> +
>>>> +     dev_dbg(cdns->dev, "Switching role");
>>>> +
>>>
>>> Don't you have to stop the previous role before starting the new role?
>>>
>>
>> Yes, it is needed. Pawel may simply some flows to suit his platform.
>>
>>>> +     ret = cdns3_role_start(cdns, role);
>>>> +     if (ret) {
>>>> +             /* Back to current role */
>>>> +             dev_err(cdns->dev, "set %d has failed, back to %d\n",
>>>> +                     role, current_role);
>>>> +             ret = cdns3_role_start(cdns, current_role);
>>>> +     }
>>>> +
>>>> +     return ret;
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_role_switch - work queue handler for role switch
>>>> + *
>>>> + * @work: work queue item structure
>>>> + *
>>>> + * Handles below events:
>>>> + * - Role switch for dual-role devices
>>>> + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
>>>> + */
>>>> +static void cdns3_role_switch(struct work_struct *work)
>>>> +{
>>>> +     enum cdns3_roles role = CDNS3_ROLE_END;
>>>> +     struct cdns3 *cdns;
>>>> +     bool device, host;
>>>> +
>>>> +     cdns = container_of(work, struct cdns3, role_switch_wq);
>>>> +
>>>> +     //TODO: implements this functions.
>>>> +     //host = cdns3_is_host(cdns);
>>>> +     //device = cdns3_is_device(cdns);
>>>> +     host = 1;
>>>> +     device = 0;
>>>> +
>>>> +     if (host)
>>>> +             role = CDNS3_ROLE_HOST;
>>>> +     else if (device)
>>>> +             role = CDNS3_ROLE_GADGET;
>>>> +
>>>> +     if (cdns->desired_dr_mode == cdns->current_dr_mode &&
>>>> +         cdns->role == role)
>>>> +             return;
>>>> +
>>>
>>> I think all the below code can be moved to cdns3_do_role_switch().
>>>
>>>> +     pm_runtime_get_sync(cdns->dev);
>>>> +     cdns3_role_stop(cdns);
>>>> +
>>>> +     if (host) {
>>>> +             if (cdns->roles[CDNS3_ROLE_HOST])
>>>> +                     cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
>>>> +             pm_runtime_put_sync(cdns->dev);
>>>> +             return;
>>>> +     }
>>>> +
>>>> +     if (device)
>>>> +             cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
>>>> +     else
>>>> +             cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
>>>> +
>>>> +     pm_runtime_put_sync(cdns->dev);
>>>> +}
>>>> +
>>>> +/**
>>>> + * cdns3_probe - probe for cdns3 core device
>>>> + * @pdev: Pointer to cdns3 core platform device
>>>> + *
>>>> + * Returns 0 on success otherwise negative errno
>>>> + */
>>>> +static int cdns3_probe(struct platform_device *pdev)
>>>> +{
>>>> +     struct device *dev = &pdev->dev;
>>>> +     struct resource *res;
>>>> +     struct cdns3 *cdns;
>>>> +     void __iomem *regs;
>>>> +     int ret;
>>>> +
>>>> +     cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
>>>> +     if (!cdns)
>>>> +             return -ENOMEM;
>>>> +
>>>> +     cdns->dev = dev;
>>>> +
>>>> +     platform_set_drvdata(pdev, cdns);
>>>> +
>>>> +     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
>>>> +     if (!res) {
>>>> +             dev_err(dev, "missing IRQ\n");
>>>> +             return -ENODEV;
>>>> +     }
>>>> +     cdns->irq = res->start;
>>>> +
>>>> +     /*
>>>> +      * Request memory region
>>>> +      * region-0: xHCI
>>>> +      * region-1: Peripheral
>>>> +      * region-2: OTG registers
>>>> +      */
>>>
>>> The memory region order is different from the dt-binding.
>>> There it is OTG, host(xhci), device (peripheral).
>>>
>>>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> +     regs = devm_ioremap_resource(dev, res);
>>>> +
>>>> +     if (IS_ERR(regs))
>>>> +             return PTR_ERR(regs);
>>>> +     cdns->xhci_regs = regs;
>>>> +     cdns->xhci_res = res;
>>>> +
>>>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>>>> +     regs = devm_ioremap_resource(dev, res);
>>>> +     if (IS_ERR(regs))
>>>> +             return PTR_ERR(regs);
>>>> +     cdns->dev_regs  = regs;
>>>> +
>>>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
>>>> +     regs = devm_ioremap_resource(dev, res);
>>>> +     if (IS_ERR(regs))
>>>> +             return PTR_ERR(regs);
>>>> +     cdns->otg_regs = regs;
>>>> +
>>>> +     mutex_init(&cdns->mutex);
>>>> +
>>>> +     cdns->phy = devm_phy_get(dev, "cdns3,usbphy");
>>>
>>> "cdns3,usbphy" is not documented in dt-binding.
>>>
>>>> +     if (IS_ERR(cdns->phy)) {
>>>> +             dev_info(dev, "no generic phy found\n");
>>>> +             cdns->phy = NULL;
>>>> +             /*
>>>> +              * fall through here!
>>>> +              * if no generic phy found, phy init
>>>> +              * should be done under boot!
>>>> +              */
>>>
>>> No you shouldn't fall through always if it is an error condition.
>>> Something like this should work better.
>>>
>>>         if (IS_ERR(cnds->phy)) {
>>>                 ret = PTR_ERR(cdns->phy);
>>>                 if (ret == -ENOSYS || ret == -ENODEV) {
>>>                         cdns->phy = NULL;
>>>                 } else if (ret == -EPROBE_DEFER) {
>>>                         return ret;
>>>                 } else {
>>>                         dev_err(dev, "no phy found\n");
>>>                         goto err0;
>>>                 }
>>>         }
>>>
>>> So if PHY was provided in DT, and PHY support/drivers is present
>>> and error condition means something is wrong and we have to error out.
>>>
>>>> +     } else {
>>>> +             phy_init(cdns->phy);
>>>> +     }
>>>
>>> You can do phy_init() outside the else.
>>>
>>>> +
>>>> +     ret = cdns3_core_init_role(cdns);
>>>> +     if (ret)
>>>> +             goto err1;
>>>> +
>>>> +     INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
>>>> +     if (ret)
>>>> +             goto err2;
>>>> +
>>>> +     if (ret)
>>>> +             goto err2;
>>>> +
>>>> +     cdns->role = cdns3_get_role(cdns);
>>>
>>> I think this should move to cdns3_core_init_role().
>>>
>>
>> I agree.
>>
>>>> +
>>>> +     ret = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
>>>> +                            dev_name(dev), cdns);
>>>> +
>>>> +     if (ret)
>>>> +             goto err2;
>>>
>>> How about moving request_irq to before cdsn3_core_init_role()?
>>>
>>> Then you can move cdns3_role_start() as well to core_init_role().
>>>
>>
>> Usually, we request irq after hardware initialization has finished, if not,
>> there may unexpected interrupt.
>
>Doesn't kernel warn if interrupt happens and there is no handler?
>To avoid that I was suggesting to request_irq first.
>
Cheers
Pawel

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

* RE: [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code.
  2018-12-05 19:42       ` Pawel Laszczak
@ 2018-12-06 10:02         ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-06 10:02 UTC (permalink / raw)
  To: Peter Chen, rogerq
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, lkml, Alan Douglas,
	jbergsagel, nsekhar, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar, Tomasz Klimek

Hi,

>>> > +
>>> > +static inline void cdns3_role_stop(struct cdns3 *cdns)
>>> > +{
>>> > +     enum cdns3_roles role = cdns->role;
>>> > +
>>> > +     if (role == CDNS3_ROLE_END)
>>>
>>> WARN_ON(role >= CNDS3_ROLE_END) ?
>>>
>>> > +             return;
>>> > +
>>> > +     mutex_lock(&cdns->mutex);
>>> > +     cdns->roles[role]->stop(cdns);
>>> > +     cdns->role = CDNS3_ROLE_END;
>>>
>>> Why change the role here? You are just stopping the role not changing it.
>>> I think cdns->role should remain unchanged, so we can call cdns3_role_start()
>>> if required without error.
>>>
>>
>>The current version of this IP has some issues to detect vbus status correctly,
>>we have to force vbus status accordingly, so we need a status to indicate
>>vbus disconnection, and add some code to let controller know vbus
>>removal, in that case, the controller's state machine can be correct.
>>So, we increase one role 'CDNS3_ROLE_END' to for this purpose.
>
>Hi, Tomek do you have any comment for this.
>We have in RTL whole OTG machine and we can read all states.

It's not the IP issue, but with PHY. I told with Tomek and he confirmed this issue.
In my testing platform I use different phy version and I don't have such issue. 

CDNS3_ROLE_END stay in driver for compatibility with Peter PHY version. 

>From otg specification we have in otg_state such bits:
>5:3	host_otg_state	"Current state of the OTG Host FSM.
>3'b000 : H_IDLE
>3'b001 : H_VBUS_ON
>3'b010 : H_VBUS_FAILED
>3'b011 : H_OTG_HOST_MODE
>3'b100 : H_HOST_MODE
>3'b101 : H_SWITCH_TO_DEVICE
>3'b110 : H_A_SUSPEND
>3'b111 : H_WAIT_VBUS_FALL"	RO	0x0
>2:0	dev_otg_state	"Current state of the OTG Device FSM.
>3'b000 : DEV_IDLE
>3'b001 : DEV_MODE
>3'b010 : DEV_SRP
>3'b011 : DEV_WAIT_VBUS_FALL
>3'b100 : DEV_SWITCH_TO_HOST
>3'b101 : DEV_WAIT_FOR_CONN"
>
>>
>>CDNS3_ROLE_GADGET: gadget mode and VBUS on
>>CDNS3_ROLE_HOST: host mode and VBUS on
>>CDNS3_ROLE_END: VBUS off, eg either host or device cable on the port.
>>
>>So, we may start role from CDNS3_ROLE_END at probe when nothing is connected,
>>and need to set role as CDNS3_ROLE_END at ->stop for further handling at
>>role switch routine.
>>
>
>>> > +     mutex_unlock(&cdns->mutex);
>>> > +}
>>> > +
>>> > +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
>>> > +{
>>> > +     if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
>>> > +             //TODO: implements selecting device/host mode
>>> > +             return CDNS3_ROLE_HOST;
>>> > +     }
>>> > +     return cdns->roles[CDNS3_ROLE_HOST]
>>> > +             ? CDNS3_ROLE_HOST
>>> > +             : CDNS3_ROLE_GADGET;

Cheers 
Pawel 



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

* RE: [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller.
  2018-12-04 22:41   ` Rob Herring
@ 2018-12-06 10:26     ` Pawel Laszczak
  0 siblings, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-06 10:26 UTC (permalink / raw)
  To: Rob Herring
  Cc: devicetree, gregkh, linux-usb, rogerq, linux-kernel,
	Alan Douglas, jbergsagel, nsekhar, nm, Suresh Punnoose,
	peter.chen, Pawel Jez, Rahul Kumar

Hi,

>On Sun, Nov 18, 2018 at 10:08:59AM +0000, Pawel Laszczak wrote:
>> Thsi patch aim at documenting USB related dt-bindings for the
>
>typo
>
>> Cadence USBSS-DRD controller.
>>
>> Signed-off-by: Pawel Laszczak <pawell@cadence.com>
>> ---
>>  .../devicetree/bindings/usb/cdns3-usb.txt       | 17 +++++++++++++++++
>>  1 file changed, 17 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/usb/cdns3-usb.txt
>>
>> diff --git a/Documentation/devicetree/bindings/usb/cdns3-usb.txt b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
>> new file mode 100644
>> index 000000000000..f9e953f32d47
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/usb/cdns3-usb.txt
>> @@ -0,0 +1,17 @@
>> +Binding for the Cadence USBSS-DRD controller
>> +
>> +Required properties:
>> +  - reg: Physical base address and size of the controller's register area.
>
>1 or 3 regions? If 3, what is each one and the order?

I will add extra description.
>
>> +  - compatible: Should contain: "cdns,usb3"
>
>Only one version and one set of bugs?
Yes, currently we have only one version. 

>
>> +  - interrupts: Interrupt specifier. Refer to interrupt bindings.
>
>How many interrupts?

Single line  for Device/Host/OTG

I will add description
>
>> +
>> +
>> +Example:
>> +	cdns3@f3000000 {
>
>usb@...
>
Ok. 

>> +		compatible = "cdns,usb3";
>> +		interrupts = <USB_IRQ  7 IRQ_TYPE_LEVEL_HIGH>;
>> +		reg = <0xf3000000 0x10000	//memory area for OTG/DRD registers
>> +			0xf3010000 0x10000	//memory area for HOST registers
>> +			0xf3020000 0x10000>;	//memory area for Device registers
>> +	};
>> +
>> --
>> 2.17.1
>>

Thanks,
Cheers
Pawel

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-11-28 12:22   ` Roger Quadros
  2018-12-01 11:11     ` Pawel Laszczak
@ 2018-12-10  2:12     ` Peter Chen
  2018-12-11 11:26       ` Sekhar Nori
  1 sibling, 1 reply; 85+ messages in thread
From: Peter Chen @ 2018-12-10  2:12 UTC (permalink / raw)
  To: rogerq
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nsekhar, nm, sureshp, peter.chen, pjez,
	kurahul, balbi

> > +static struct cdns3_endpoint *cdns3_find_available_ss_ep(struct cdns3_device *priv_dev,
> > +                                                      struct usb_endpoint_descriptor *desc)
>
> why is this function called ss_ep? This doesn't seem like only for superspeed endpoints.
>

Yes, it is for all speed.

> > +{
> > +     struct usb_ep *ep;
> > +     struct cdns3_endpoint *priv_ep;
> > +
> > +     list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
> > +             unsigned long num;
> > +             int ret;
> > +             /* ep name pattern likes epXin or epXout */
> > +             char c[2] = {ep->name[2], '\0'};
> > +
> > +             ret = kstrtoul(c, 10, &num);
> > +             if (ret)
> > +                     return ERR_PTR(ret);
> > +
> > +             priv_ep = ep_to_cdns3_ep(ep);
> > +             if (cdns3_ep_dir_is_correct(desc, priv_ep)) {
> > +                     if (!(priv_ep->flags & EP_USED)) {
> > +                             priv_ep->num  = num;
> > +                             priv_ep->flags |= EP_USED;
> > +                             return priv_ep;
> > +                     }
> > +             }
> > +     }
> > +     return ERR_PTR(-ENOENT);
> > +}
> > +
> > +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
> > +                                         struct usb_endpoint_descriptor *desc,
> > +                                         struct usb_ss_ep_comp_descriptor *comp_desc)
> > +{
> > +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> > +     struct cdns3_endpoint *priv_ep;
> > +     unsigned long flags;
> > +
> > +     priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
> > +     if (IS_ERR(priv_ep)) {
> > +             dev_err(&priv_dev->dev, "no available ep\n");
> > +             return NULL;
> > +     }
> > +
> > +     dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
> > +
> > +     spin_lock_irqsave(&priv_dev->lock, flags);
> > +     priv_ep->endpoint.desc = desc;
> > +     priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
> > +     priv_ep->type = usb_endpoint_type(desc);
> > +
> > +     list_add_tail(&priv_ep->ep_match_pending_list,
> > +                   &priv_dev->ep_match_list);
> > +     spin_unlock_irqrestore(&priv_dev->lock, flags);
> > +     return &priv_ep->endpoint;
> > +}
>
> Why do you need a custom match_ep?
> doesn't usb_ep_autoconfig suffice?
>
> You can check if EP is claimed or not by checking the ep->claimed flag.
>

It is a special requirement for this IP, the EP type and MaxPacketSize
changing can't be done at runtime, eg, at ep->enable. See below commit
for detail.

    usb: cdns3: gadget: configure all endpoints before set configuration

    Cadence IP has one limitation that all endpoints must be configured
    (Type & MaxPacketSize) before setting configuration through hardware
    register, it means we can't change endpoints configuration after
    set_configuration.

    In this patch, we add non-control endpoint through usb_ss->ep_match_list,
    which is added when the gadget driver uses usb_ep_autoconfig to configure
    specific endpoint; When the udc driver receives set_configurion request,
    it goes through usb_ss->ep_match_list, and configure all endpoints
    accordingly.

    At usb_ep_ops.enable/disable, we only enable and disable endpoint through
    ep_cfg register which can be changed after set_configuration, and do
    some software operation accordingly.


> > +
> > +/**
> > + * cdns3_gadget_get_frame Returns number of actual ITP frame
> > + * @gadget: gadget object
> > + *
> > + * Returns number of actual ITP frame
> > + */
> > +static int cdns3_gadget_get_frame(struct usb_gadget *gadget)
> > +{
> > +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> > +
> > +     return readl(&priv_dev->regs->usb_iptn);
> > +}
> > +
> > +static int cdns3_gadget_wakeup(struct usb_gadget *gadget)
> > +{
> > +     return 0;
> > +}
> > +
> > +static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget,
> > +                                     int is_selfpowered)
> > +{
> > +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> > +     unsigned long flags;
> > +
> > +     spin_lock_irqsave(&priv_dev->lock, flags);
> > +     gadget->is_selfpowered = !!is_selfpowered;
> > +     spin_unlock_irqrestore(&priv_dev->lock, flags);
> > +     return 0;
> > +}
> > +
> > +static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on)
> > +{
> > +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> > +
> > +     if (!priv_dev->start_gadget)
> > +             return 0;
> > +
> > +     if (is_on)
> > +             writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf);
> > +     else
> > +             writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
> > +
> > +     return 0;
> > +}
> > +
> >  static void cdns3_gadget_config(struct cdns3_device *priv_dev)
> >  {
> >       struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
> > @@ -74,6 +231,95 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev)
> >       writel(USB_CONF_DEVEN, &regs->usb_conf);
> >  }
> >
> > +/**
> > + * cdns3_gadget_udc_start Gadget start
> > + * @gadget: gadget object
> > + * @driver: driver which operates on this gadget
> > + *
> > + * Returns 0 on success, error code elsewhere
> > + */
> > +static int cdns3_gadget_udc_start(struct usb_gadget *gadget,
> > +                               struct usb_gadget_driver *driver)
> > +{
> > +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> > +     unsigned long flags;
> > +
> > +     if (priv_dev->gadget_driver) {
> > +             dev_err(&priv_dev->dev, "%s is already bound to %s\n",
> > +                     priv_dev->gadget.name,
> > +                     priv_dev->gadget_driver->driver.name);
> > +             return -EBUSY;
> > +     }
>
> Not sure if this check is required. UDC core should be doing that.
>

Yes, UDC core has done this.

> > +
> > +     spin_lock_irqsave(&priv_dev->lock, flags);
> > +     priv_dev->gadget_driver = driver;
> > +     if (!priv_dev->start_gadget)
> > +             goto unlock;
> > +
> > +     cdns3_gadget_config(priv_dev);
> > +unlock:
> > +     spin_unlock_irqrestore(&priv_dev->lock, flags);
> > +     return 0;
> > +}
> > +
> > +/**
> > + * cdns3_gadget_udc_stop Stops gadget
> > + * @gadget: gadget object
> > + *
> > + * Returns 0
> > + */
> > +static int cdns3_gadget_udc_stop(struct usb_gadget *gadget)
> > +{
> > +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> > +     struct cdns3_endpoint *priv_ep, *temp_ep;
> > +     u32 bEndpointAddress;
> > +     struct usb_ep *ep;
> > +     int ret = 0;
> > +     int i;
> > +
> > +     priv_dev->gadget_driver = NULL;
> > +     list_for_each_entry_safe(priv_ep, temp_ep, &priv_dev->ep_match_list,
> > +                              ep_match_pending_list) {
> > +             list_del(&priv_ep->ep_match_pending_list);
> > +             priv_ep->flags &= ~EP_USED;
> > +     }
> > +
> > +     priv_dev->onchip_mem_allocated_size = 0;
> > +     priv_dev->out_mem_is_allocated = 0;
> > +     priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> > +
> > +     for (i = 0; i < priv_dev->ep_nums ; i++)
> > +             cdns3_free_trb_pool(priv_dev->eps[i]);
> > +
> > +     if (!priv_dev->start_gadget)
> > +             return 0;
>
> This looks tricky. Why do we need this flag?

We only start the gadget (enable clocks) when the VBUS is on, so if we load
class driver without VBUS, we only do  software .bind but without
hardware operation.

>
> > +
> > +     list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) {
> > +             priv_ep = ep_to_cdns3_ep(ep);
> > +             bEndpointAddress = priv_ep->num | priv_ep->dir;
> > +             cdns3_select_ep(priv_dev, bEndpointAddress);
> > +             writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd);
> > +             ret = cdns3_handshake(&priv_dev->regs->ep_cmd,
> > +                                   EP_CMD_EPRST, 0, 100);
> > +     }
> > +
> > +     /* disable interrupt for device */
> > +     writel(0, &priv_dev->regs->usb_ien);
> > +     writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf);
>
> where are you requesting the interrupt? Looks like it should be done in
> udc_start() no?
>

Yes, it is at udc_start.

Peter

> > +
> > +     return ret;
> > +}
>
> Can we combine cdns3_gadget_udc_start() and cdns3_gadget_udc_start()
> with cdns3_gadget_start() and cdns3_gadget_stop() respectively so that
> cdns3_gadget_config() and cleanup() is done at one place.
>
> > +
> > +static const struct usb_gadget_ops cdns3_gadget_ops = {
> > +     .get_frame = cdns3_gadget_get_frame,
> > +     .wakeup = cdns3_gadget_wakeup,
> > +     .set_selfpowered = cdns3_gadget_set_selfpowered,
> > +     .pullup = cdns3_gadget_pullup,
> > +     .udc_start = cdns3_gadget_udc_start,
> > +     .udc_stop = cdns3_gadget_udc_stop,
> > +     .match_ep = cdns3_gadget_match_ep,
> > +};
> > +
> >  /**
> >   * cdns3_init_ep Initializes software endpoints of gadget
> >   * @cdns3: extended gadget object
> > @@ -184,8 +430,7 @@ static int __cdns3_gadget_init(struct cdns3 *cdns)
> >       /* fill gadget fields */
> >       priv_dev->gadget.max_speed = USB_SPEED_SUPER;
> >       priv_dev->gadget.speed = USB_SPEED_UNKNOWN;
> > -     //TODO: Add implementation of cdns3_gadget_ops
> > -     //priv_dev->gadget.ops = &cdns3_gadget_ops;
> > +     priv_dev->gadget.ops = &cdns3_gadget_ops;
> >       priv_dev->gadget.name = "usb-ss-gadget";
> >       priv_dev->gadget.sg_supported = 1;
> >       priv_dev->is_connected = 0;
> >
>
> cheers,
> -roger
>
> --
> Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki.
> Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-10  2:12     ` Peter Chen
@ 2018-12-11 11:26       ` Sekhar Nori
  2018-12-11 19:49         ` Pawel Laszczak
  0 siblings, 1 reply; 85+ messages in thread
From: Sekhar Nori @ 2018-12-11 11:26 UTC (permalink / raw)
  To: Peter Chen, rogerq
  Cc: pawell, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul,
	balbi

On 10/12/18 7:42 AM, Peter Chen wrote:
>>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>>> +                                         struct usb_endpoint_descriptor *desc,
>>> +                                         struct usb_ss_ep_comp_descriptor *comp_desc)
>>> +{
>>> +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>>> +     struct cdns3_endpoint *priv_ep;
>>> +     unsigned long flags;
>>> +
>>> +     priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
>>> +     if (IS_ERR(priv_ep)) {
>>> +             dev_err(&priv_dev->dev, "no available ep\n");
>>> +             return NULL;
>>> +     }
>>> +
>>> +     dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
>>> +
>>> +     spin_lock_irqsave(&priv_dev->lock, flags);
>>> +     priv_ep->endpoint.desc = desc;
>>> +     priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
>>> +     priv_ep->type = usb_endpoint_type(desc);
>>> +
>>> +     list_add_tail(&priv_ep->ep_match_pending_list,
>>> +                   &priv_dev->ep_match_list);
>>> +     spin_unlock_irqrestore(&priv_dev->lock, flags);
>>> +     return &priv_ep->endpoint;
>>> +}
>> Why do you need a custom match_ep?
>> doesn't usb_ep_autoconfig suffice?
>>
>> You can check if EP is claimed or not by checking the ep->claimed flag.
>>
> It is a special requirement for this IP, the EP type and MaxPacketSize
> changing can't be done at runtime, eg, at ep->enable. See below commit
> for detail.
> 
>     usb: cdns3: gadget: configure all endpoints before set configuration
> 
>     Cadence IP has one limitation that all endpoints must be configured
>     (Type & MaxPacketSize) before setting configuration through hardware
>     register, it means we can't change endpoints configuration after
>     set_configuration.
> 
>     In this patch, we add non-control endpoint through usb_ss->ep_match_list,
>     which is added when the gadget driver uses usb_ep_autoconfig to configure
>     specific endpoint; When the udc driver receives set_configurion request,
>     it goes through usb_ss->ep_match_list, and configure all endpoints
>     accordingly.
> 
>     At usb_ep_ops.enable/disable, we only enable and disable endpoint through
>     ep_cfg register which can be changed after set_configuration, and do
>     some software operation accordingly.

All this should be part of comments in code along with information about
controller versions which suffer from the errata.

Is there a version of controller available which does not have the
defect? Is there a future plan to fix this?

If any of that is yes, you probably want to handle this with runtime
detection of version (like done with DWC3_REVISION_XXX macros).
Sometimes the hardware-read versions themselves are incorrect, so its
better to introduce a version specific compatible too like
"cdns,usb-1.0.0" (as hinted to by Rob Herring as well).

Thanks,
Sekhar

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

* RE: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-11 11:26       ` Sekhar Nori
@ 2018-12-11 19:49         ` Pawel Laszczak
  2018-12-14  1:34           ` Peter Chen
  0 siblings, 1 reply; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-11 19:49 UTC (permalink / raw)
  To: Sekhar Nori, Peter Chen, rogerq
  Cc: devicetree, Greg Kroah-Hartman, linux-usb, lkml, Alan Douglas,
	jbergsagel, nm, Suresh Punnoose, peter.chen, Pawel Jez,
	Rahul Kumar, balbi

Hi,

>On 10/12/18 7:42 AM, Peter Chen wrote:
>>>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>>>> +                                         struct usb_endpoint_descriptor *desc,
>>>> +                                         struct usb_ss_ep_comp_descriptor *comp_desc)
>>>> +{
>>>> +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>>>> +     struct cdns3_endpoint *priv_ep;
>>>> +     unsigned long flags;
>>>> +
>>>> +     priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
>>>> +     if (IS_ERR(priv_ep)) {
>>>> +             dev_err(&priv_dev->dev, "no available ep\n");
>>>> +             return NULL;
>>>> +     }
>>>> +
>>>> +     dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
>>>> +
>>>> +     spin_lock_irqsave(&priv_dev->lock, flags);
>>>> +     priv_ep->endpoint.desc = desc;
>>>> +     priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
>>>> +     priv_ep->type = usb_endpoint_type(desc);
>>>> +
>>>> +     list_add_tail(&priv_ep->ep_match_pending_list,
>>>> +                   &priv_dev->ep_match_list);
>>>> +     spin_unlock_irqrestore(&priv_dev->lock, flags);
>>>> +     return &priv_ep->endpoint;
>>>> +}
>>> Why do you need a custom match_ep?
>>> doesn't usb_ep_autoconfig suffice?
>>>
>>> You can check if EP is claimed or not by checking the ep->claimed flag.
>>>
>> It is a special requirement for this IP, the EP type and MaxPacketSize
>> changing can't be done at runtime, eg, at ep->enable. See below commit
>> for detail.
>>
>>     usb: cdns3: gadget: configure all endpoints before set configuration
>>
>>     Cadence IP has one limitation that all endpoints must be configured
>>     (Type & MaxPacketSize) before setting configuration through hardware
>>     register, it means we can't change endpoints configuration after
>>     set_configuration.
>>
>>     In this patch, we add non-control endpoint through usb_ss->ep_match_list,
>>     which is added when the gadget driver uses usb_ep_autoconfig to configure
>>     specific endpoint; When the udc driver receives set_configurion request,
>>     it goes through usb_ss->ep_match_list, and configure all endpoints
>>     accordingly.
>>
>>     At usb_ep_ops.enable/disable, we only enable and disable endpoint through
>>     ep_cfg register which can be changed after set_configuration, and do
>>     some software operation accordingly.
>
>All this should be part of comments in code along with information about
>controller versions which suffer from the errata.
>
>Is there a version of controller available which does not have the
>defect? Is there a future plan to fix this?
>
>If any of that is yes, you probably want to handle this with runtime
>detection of version (like done with DWC3_REVISION_XXX macros).
>Sometimes the hardware-read versions themselves are incorrect, so its
>better to introduce a version specific compatible too like
>"cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>

custom match_ep is used and works with all versions of the gen1 
controller. Future (gen2) releases of the controller won’t have such 
limitation but there is no plan to change current (gen1) functionality 
of the controller.

I will add comment before cdns3_gadget_match_ep function.
Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
cdns,usb3-1.0.1 compatible. 

cdns,usb3-1.0.1 will be for current version of controller which I use.
cdns,usb3-1.0.0 will be for older version - Peter Chan platform. 
I now that I have some changes in controller, and one of them require 
some changes in DRD driver. It will be safer to add two separate 
version in compatibles.

Thanks
 Pawel

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-11 19:49         ` Pawel Laszczak
@ 2018-12-14  1:34           ` Peter Chen
  2018-12-14  6:49             ` Pawel Laszczak
  2018-12-14 10:39             ` Sekhar Nori
  0 siblings, 2 replies; 85+ messages in thread
From: Peter Chen @ 2018-12-14  1:34 UTC (permalink / raw)
  To: pawell
  Cc: nsekhar, rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul,
	balbi

On Wed, Dec 12, 2018 at 3:49 AM Pawel Laszczak <pawell@cadence.com> wrote:
>
> Hi,
>
> >On 10/12/18 7:42 AM, Peter Chen wrote:
> >>>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
> >>>> +                                         struct usb_endpoint_descriptor *desc,
> >>>> +                                         struct usb_ss_ep_comp_descriptor *comp_desc)
> >>>> +{
> >>>> +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
> >>>> +     struct cdns3_endpoint *priv_ep;
> >>>> +     unsigned long flags;
> >>>> +
> >>>> +     priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
> >>>> +     if (IS_ERR(priv_ep)) {
> >>>> +             dev_err(&priv_dev->dev, "no available ep\n");
> >>>> +             return NULL;
> >>>> +     }
> >>>> +
> >>>> +     dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
> >>>> +
> >>>> +     spin_lock_irqsave(&priv_dev->lock, flags);
> >>>> +     priv_ep->endpoint.desc = desc;
> >>>> +     priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
> >>>> +     priv_ep->type = usb_endpoint_type(desc);
> >>>> +
> >>>> +     list_add_tail(&priv_ep->ep_match_pending_list,
> >>>> +                   &priv_dev->ep_match_list);
> >>>> +     spin_unlock_irqrestore(&priv_dev->lock, flags);
> >>>> +     return &priv_ep->endpoint;
> >>>> +}
> >>> Why do you need a custom match_ep?
> >>> doesn't usb_ep_autoconfig suffice?
> >>>
> >>> You can check if EP is claimed or not by checking the ep->claimed flag.
> >>>
> >> It is a special requirement for this IP, the EP type and MaxPacketSize
> >> changing can't be done at runtime, eg, at ep->enable. See below commit
> >> for detail.
> >>
> >>     usb: cdns3: gadget: configure all endpoints before set configuration
> >>
> >>     Cadence IP has one limitation that all endpoints must be configured
> >>     (Type & MaxPacketSize) before setting configuration through hardware
> >>     register, it means we can't change endpoints configuration after
> >>     set_configuration.
> >>
> >>     In this patch, we add non-control endpoint through usb_ss->ep_match_list,
> >>     which is added when the gadget driver uses usb_ep_autoconfig to configure
> >>     specific endpoint; When the udc driver receives set_configurion request,
> >>     it goes through usb_ss->ep_match_list, and configure all endpoints
> >>     accordingly.
> >>
> >>     At usb_ep_ops.enable/disable, we only enable and disable endpoint through
> >>     ep_cfg register which can be changed after set_configuration, and do
> >>     some software operation accordingly.
> >
> >All this should be part of comments in code along with information about
> >controller versions which suffer from the errata.
> >
> >Is there a version of controller available which does not have the
> >defect? Is there a future plan to fix this?
> >
> >If any of that is yes, you probably want to handle this with runtime
> >detection of version (like done with DWC3_REVISION_XXX macros).
> >Sometimes the hardware-read versions themselves are incorrect, so its
> >better to introduce a version specific compatible too like
> >"cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
> >
>
> custom match_ep is used and works with all versions of the gen1
> controller. Future (gen2) releases of the controller won’t have such
> limitation but there is no plan to change current (gen1) functionality
> of the controller.
>
> I will add comment before cdns3_gadget_match_ep function.
> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
> cdns,usb3-1.0.1 compatible.
>
> cdns,usb3-1.0.1 will be for current version of controller which I use.
> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
> I now that I have some changes in controller, and one of them require
> some changes in DRD driver. It will be safer to add two separate
> version in compatibles.
>

Pawel, could we have correct register to show controller version? It is
better we could version judgement at runtime instead of static compatible.

Peter

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

* RE: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14  1:34           ` Peter Chen
@ 2018-12-14  6:49             ` Pawel Laszczak
  2018-12-14 10:39             ` Sekhar Nori
  1 sibling, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-14  6:49 UTC (permalink / raw)
  To: Peter Chen
  Cc: nsekhar, rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	Alan Douglas, jbergsagel, nm, Suresh Punnoose, peter.chen,
	Pawel Jez, Rahul Kumar, balbi

Hi,
>
>On Wed, Dec 12, 2018 at 3:49 AM Pawel Laszczak <pawell@cadence.com> wrote:
>>
>> Hi,
>>
>> >On 10/12/18 7:42 AM, Peter Chen wrote:
>> >>>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>> >>>> +                                         struct usb_endpoint_descriptor *desc,
>> >>>> +                                         struct usb_ss_ep_comp_descriptor *comp_desc)
>> >>>> +{
>> >>>> +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>> >>>> +     struct cdns3_endpoint *priv_ep;
>> >>>> +     unsigned long flags;
>> >>>> +
>> >>>> +     priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
>> >>>> +     if (IS_ERR(priv_ep)) {
>> >>>> +             dev_err(&priv_dev->dev, "no available ep\n");
>> >>>> +             return NULL;
>> >>>> +     }
>> >>>> +
>> >>>> +     dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
>> >>>> +
>> >>>> +     spin_lock_irqsave(&priv_dev->lock, flags);
>> >>>> +     priv_ep->endpoint.desc = desc;
>> >>>> +     priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
>> >>>> +     priv_ep->type = usb_endpoint_type(desc);
>> >>>> +
>> >>>> +     list_add_tail(&priv_ep->ep_match_pending_list,
>> >>>> +                   &priv_dev->ep_match_list);
>> >>>> +     spin_unlock_irqrestore(&priv_dev->lock, flags);
>> >>>> +     return &priv_ep->endpoint;
>> >>>> +}
>> >>> Why do you need a custom match_ep?
>> >>> doesn't usb_ep_autoconfig suffice?
>> >>>
>> >>> You can check if EP is claimed or not by checking the ep->claimed flag.
>> >>>
>> >> It is a special requirement for this IP, the EP type and MaxPacketSize
>> >> changing can't be done at runtime, eg, at ep->enable. See below commit
>> >> for detail.
>> >>
>> >>     usb: cdns3: gadget: configure all endpoints before set configuration
>> >>
>> >>     Cadence IP has one limitation that all endpoints must be configured
>> >>     (Type & MaxPacketSize) before setting configuration through hardware
>> >>     register, it means we can't change endpoints configuration after
>> >>     set_configuration.
>> >>
>> >>     In this patch, we add non-control endpoint through usb_ss->ep_match_list,
>> >>     which is added when the gadget driver uses usb_ep_autoconfig to configure
>> >>     specific endpoint; When the udc driver receives set_configurion request,
>> >>     it goes through usb_ss->ep_match_list, and configure all endpoints
>> >>     accordingly.
>> >>
>> >>     At usb_ep_ops.enable/disable, we only enable and disable endpoint through
>> >>     ep_cfg register which can be changed after set_configuration, and do
>> >>     some software operation accordingly.
>> >
>> >All this should be part of comments in code along with information about
>> >controller versions which suffer from the errata.
>> >
>> >Is there a version of controller available which does not have the
>> >defect? Is there a future plan to fix this?
>> >
>> >If any of that is yes, you probably want to handle this with runtime
>> >detection of version (like done with DWC3_REVISION_XXX macros).
>> >Sometimes the hardware-read versions themselves are incorrect, so its
>> >better to introduce a version specific compatible too like
>> >"cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>> >
>>
>> custom match_ep is used and works with all versions of the gen1
>> controller. Future (gen2) releases of the controller won’t have such
>> limitation but there is no plan to change current (gen1) functionality
>> of the controller.
>>
>> I will add comment before cdns3_gadget_match_ep function.
>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>> cdns,usb3-1.0.1 compatible.
>>
>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>> I now that I have some changes in controller, and one of them require
>> some changes in DRD driver. It will be safer to add two separate
>> version in compatibles.
>>
>
>Pawel, could we have correct register to show controller version? It is
>better we could version judgement at runtime instead of static compatible.
>

Ok, I will tray do this in this way.

Pawel 

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14  1:34           ` Peter Chen
  2018-12-14  6:49             ` Pawel Laszczak
@ 2018-12-14 10:39             ` Sekhar Nori
  2018-12-14 10:47               ` Felipe Balbi
  1 sibling, 1 reply; 85+ messages in thread
From: Sekhar Nori @ 2018-12-14 10:39 UTC (permalink / raw)
  To: Peter Chen, pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul,
	balbi

On 14/12/18 7:04 AM, Peter Chen wrote:
> On Wed, Dec 12, 2018 at 3:49 AM Pawel Laszczak <pawell@cadence.com> wrote:
>>
>> Hi,
>>
>>> On 10/12/18 7:42 AM, Peter Chen wrote:
>>>>>> +static struct usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
>>>>>> +                                         struct usb_endpoint_descriptor *desc,
>>>>>> +                                         struct usb_ss_ep_comp_descriptor *comp_desc)
>>>>>> +{
>>>>>> +     struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget);
>>>>>> +     struct cdns3_endpoint *priv_ep;
>>>>>> +     unsigned long flags;
>>>>>> +
>>>>>> +     priv_ep = cdns3_find_available_ss_ep(priv_dev, desc);
>>>>>> +     if (IS_ERR(priv_ep)) {
>>>>>> +             dev_err(&priv_dev->dev, "no available ep\n");
>>>>>> +             return NULL;
>>>>>> +     }
>>>>>> +
>>>>>> +     dev_dbg(&priv_dev->dev, "match endpoint: %s\n", priv_ep->name);
>>>>>> +
>>>>>> +     spin_lock_irqsave(&priv_dev->lock, flags);
>>>>>> +     priv_ep->endpoint.desc = desc;
>>>>>> +     priv_ep->dir  = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT;
>>>>>> +     priv_ep->type = usb_endpoint_type(desc);
>>>>>> +
>>>>>> +     list_add_tail(&priv_ep->ep_match_pending_list,
>>>>>> +                   &priv_dev->ep_match_list);
>>>>>> +     spin_unlock_irqrestore(&priv_dev->lock, flags);
>>>>>> +     return &priv_ep->endpoint;
>>>>>> +}
>>>>> Why do you need a custom match_ep?
>>>>> doesn't usb_ep_autoconfig suffice?
>>>>>
>>>>> You can check if EP is claimed or not by checking the ep->claimed flag.
>>>>>
>>>> It is a special requirement for this IP, the EP type and MaxPacketSize
>>>> changing can't be done at runtime, eg, at ep->enable. See below commit
>>>> for detail.
>>>>
>>>>     usb: cdns3: gadget: configure all endpoints before set configuration
>>>>
>>>>     Cadence IP has one limitation that all endpoints must be configured
>>>>     (Type & MaxPacketSize) before setting configuration through hardware
>>>>     register, it means we can't change endpoints configuration after
>>>>     set_configuration.
>>>>
>>>>     In this patch, we add non-control endpoint through usb_ss->ep_match_list,
>>>>     which is added when the gadget driver uses usb_ep_autoconfig to configure
>>>>     specific endpoint; When the udc driver receives set_configurion request,
>>>>     it goes through usb_ss->ep_match_list, and configure all endpoints
>>>>     accordingly.
>>>>
>>>>     At usb_ep_ops.enable/disable, we only enable and disable endpoint through
>>>>     ep_cfg register which can be changed after set_configuration, and do
>>>>     some software operation accordingly.
>>>
>>> All this should be part of comments in code along with information about
>>> controller versions which suffer from the errata.
>>>
>>> Is there a version of controller available which does not have the
>>> defect? Is there a future plan to fix this?
>>>
>>> If any of that is yes, you probably want to handle this with runtime
>>> detection of version (like done with DWC3_REVISION_XXX macros).
>>> Sometimes the hardware-read versions themselves are incorrect, so its
>>> better to introduce a version specific compatible too like
>>> "cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>>>
>>
>> custom match_ep is used and works with all versions of the gen1
>> controller. Future (gen2) releases of the controller won’t have such
>> limitation but there is no plan to change current (gen1) functionality
>> of the controller.
>>
>> I will add comment before cdns3_gadget_match_ep function.
>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>> cdns,usb3-1.0.1 compatible.
>>
>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>> I now that I have some changes in controller, and one of them require
>> some changes in DRD driver. It will be safer to add two separate
>> version in compatibles.
>>
> 
> Pawel, could we have correct register to show controller version? It is
> better we could version judgement at runtime instead of static compatible.

Agree with detecting IP version at runtime.

But please have some indication of version in compatible string too,
especially since you already know there is going to be another revision
of hardware. It has the advantage that one can easily grep to see which
hardware is running current version of controller without having access
to the hardware itself. Becomes useful later on when its time to
clean-up unused code when boards become obsolete or for requesting
testing help.

Thanks,
Sekhar

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14 10:39             ` Sekhar Nori
@ 2018-12-14 10:47               ` Felipe Balbi
  2018-12-14 11:13                 ` Sekhar Nori
  0 siblings, 1 reply; 85+ messages in thread
From: Felipe Balbi @ 2018-12-14 10:47 UTC (permalink / raw)
  To: Sekhar Nori, Peter Chen, pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul

[-- Attachment #1: Type: text/plain, Size: 2741 bytes --]


Hi,

Sekhar Nori <nsekhar@ti.com> writes:

<snip>

>>>> All this should be part of comments in code along with information about
>>>> controller versions which suffer from the errata.
>>>>
>>>> Is there a version of controller available which does not have the
>>>> defect? Is there a future plan to fix this?
>>>>
>>>> If any of that is yes, you probably want to handle this with runtime
>>>> detection of version (like done with DWC3_REVISION_XXX macros).
>>>> Sometimes the hardware-read versions themselves are incorrect, so its
>>>> better to introduce a version specific compatible too like
>>>> "cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>>>>
>>>
>>> custom match_ep is used and works with all versions of the gen1
>>> controller. Future (gen2) releases of the controller won’t have such
>>> limitation but there is no plan to change current (gen1) functionality
>>> of the controller.
>>>
>>> I will add comment before cdns3_gadget_match_ep function.
>>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>>> cdns,usb3-1.0.1 compatible.
>>>
>>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>>> I now that I have some changes in controller, and one of them require
>>> some changes in DRD driver. It will be safer to add two separate
>>> version in compatibles.
>>>
>> 
>> Pawel, could we have correct register to show controller version? It is
>> better we could version judgement at runtime instead of static compatible.
>
> Agree with detecting IP version at runtime.
>
> But please have some indication of version in compatible string too,

why? Runtime detection by revision register should be the way to go if
the HW provides it. Why duplicate the information in compatible string?

> especially since you already know there is going to be another revision
> of hardware. It has the advantage that one can easily grep to see which
> hardware is running current version of controller without having access
> to the hardware itself. Becomes useful later on when its time to
> clean-up unused code when boards become obsolete or for requesting
> testing help.

This doesn't sound like a very strong argument, actually. Specially when
you consider that, since driver will do revision checking based on
revision register, you already have strings to grep. Moreover, we don't
usually drop support just like that.

Personally, I wouldn't bother. Just like dwc3 never bothered and nothing
bad came about because of it. Well, there are quirks which are
undetectable and for those we have quirk flags. I much prefer that
arrangement.

-- 
balbi

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14 10:47               ` Felipe Balbi
@ 2018-12-14 11:13                 ` Sekhar Nori
  2018-12-14 11:26                   ` Felipe Balbi
  0 siblings, 1 reply; 85+ messages in thread
From: Sekhar Nori @ 2018-12-14 11:13 UTC (permalink / raw)
  To: Felipe Balbi, Peter Chen, pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul

Hi Felipe,

On 14/12/18 4:17 PM, Felipe Balbi wrote:
> Hi,
> 
> Sekhar Nori <nsekhar@ti.com> writes:
> 
> <snip>
> 
>>>>> All this should be part of comments in code along with information about
>>>>> controller versions which suffer from the errata.
>>>>>
>>>>> Is there a version of controller available which does not have the
>>>>> defect? Is there a future plan to fix this?
>>>>>
>>>>> If any of that is yes, you probably want to handle this with runtime
>>>>> detection of version (like done with DWC3_REVISION_XXX macros).
>>>>> Sometimes the hardware-read versions themselves are incorrect, so its
>>>>> better to introduce a version specific compatible too like
>>>>> "cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>>>>>
>>>>
>>>> custom match_ep is used and works with all versions of the gen1
>>>> controller. Future (gen2) releases of the controller won’t have such
>>>> limitation but there is no plan to change current (gen1) functionality
>>>> of the controller.
>>>>
>>>> I will add comment before cdns3_gadget_match_ep function.
>>>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>>>> cdns,usb3-1.0.1 compatible.
>>>>
>>>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>>>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>>>> I now that I have some changes in controller, and one of them require
>>>> some changes in DRD driver. It will be safer to add two separate
>>>> version in compatibles.
>>>>
>>>
>>> Pawel, could we have correct register to show controller version? It is
>>> better we could version judgement at runtime instead of static compatible.
>>
>> Agree with detecting IP version at runtime.
>>
>> But please have some indication of version in compatible string too,
> 
> why? Runtime detection by revision register should be the way to go if
> the HW provides it. Why duplicate the information in compatible string?
> 
>> especially since you already know there is going to be another revision
>> of hardware. It has the advantage that one can easily grep to see which
>> hardware is running current version of controller without having access
>> to the hardware itself. Becomes useful later on when its time to
>> clean-up unused code when boards become obsolete or for requesting
>> testing help.
> 
> This doesn't sound like a very strong argument, actually. Specially when
> you consider that, since driver will do revision checking based on
> revision register, you already have strings to grep. Moreover, we don't
> usually drop support just like that.

AFAICS, it is impossible to know just by grep'ing if there is any
hardware still supported in kernel and using DWC3_REVISION_194A, for
example.

If we are never going to drop support for any revision, this does not
matter much.

Also, once you have the controller supported behind PCI, then I guess
you are pretty much tied to having to read hardware revision at runtime.

> Personally, I wouldn't bother. Just like dwc3 never bothered and nothing
> bad came about because of it. Well, there are quirks which are
> undetectable and for those we have quirk flags. I much prefer that
> arrangement.

Yes, quirk flags will work too for undetectable quirks.

Thanks,
Sekhar

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14 11:13                 ` Sekhar Nori
@ 2018-12-14 11:26                   ` Felipe Balbi
  2018-12-14 12:20                     ` Sekhar Nori
  0 siblings, 1 reply; 85+ messages in thread
From: Felipe Balbi @ 2018-12-14 11:26 UTC (permalink / raw)
  To: Sekhar Nori, Peter Chen, pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul

[-- Attachment #1: Type: text/plain, Size: 3118 bytes --]


Hi,

Sekhar Nori <nsekhar@ti.com> writes:
>>>>>> All this should be part of comments in code along with information about
>>>>>> controller versions which suffer from the errata.
>>>>>>
>>>>>> Is there a version of controller available which does not have the
>>>>>> defect? Is there a future plan to fix this?
>>>>>>
>>>>>> If any of that is yes, you probably want to handle this with runtime
>>>>>> detection of version (like done with DWC3_REVISION_XXX macros).
>>>>>> Sometimes the hardware-read versions themselves are incorrect, so its
>>>>>> better to introduce a version specific compatible too like
>>>>>> "cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>>>>>>
>>>>>
>>>>> custom match_ep is used and works with all versions of the gen1
>>>>> controller. Future (gen2) releases of the controller won’t have such
>>>>> limitation but there is no plan to change current (gen1) functionality
>>>>> of the controller.
>>>>>
>>>>> I will add comment before cdns3_gadget_match_ep function.
>>>>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>>>>> cdns,usb3-1.0.1 compatible.
>>>>>
>>>>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>>>>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>>>>> I now that I have some changes in controller, and one of them require
>>>>> some changes in DRD driver. It will be safer to add two separate
>>>>> version in compatibles.
>>>>>
>>>>
>>>> Pawel, could we have correct register to show controller version? It is
>>>> better we could version judgement at runtime instead of static compatible.
>>>
>>> Agree with detecting IP version at runtime.
>>>
>>> But please have some indication of version in compatible string too,
>> 
>> why? Runtime detection by revision register should be the way to go if
>> the HW provides it. Why duplicate the information in compatible string?
>> 
>>> especially since you already know there is going to be another revision
>>> of hardware. It has the advantage that one can easily grep to see which
>>> hardware is running current version of controller without having access
>>> to the hardware itself. Becomes useful later on when its time to
>>> clean-up unused code when boards become obsolete or for requesting
>>> testing help.
>> 
>> This doesn't sound like a very strong argument, actually. Specially when
>> you consider that, since driver will do revision checking based on
>> revision register, you already have strings to grep. Moreover, we don't
>> usually drop support just like that.
>
> AFAICS, it is impossible to know just by grep'ing if there is any
> hardware still supported in kernel and using DWC3_REVISION_194A, for
> example.

but why do you even care? 

> If we are never going to drop support for any revision, this does not
> matter much.
>
> Also, once you have the controller supported behind PCI, then I guess
> you are pretty much tied to having to read hardware revision at runtime.

that's another argument *for* using runtime detection, not against it.

-- 
balbi

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14 11:26                   ` Felipe Balbi
@ 2018-12-14 12:20                     ` Sekhar Nori
  2018-12-14 12:30                       ` Felipe Balbi
  2018-12-16 13:31                       ` Pawel Laszczak
  0 siblings, 2 replies; 85+ messages in thread
From: Sekhar Nori @ 2018-12-14 12:20 UTC (permalink / raw)
  To: Felipe Balbi, Peter Chen, pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul

On 14/12/18 4:56 PM, Felipe Balbi wrote:
> Hi,
> 
> Sekhar Nori <nsekhar@ti.com> writes:
>>>>>>> All this should be part of comments in code along with information about
>>>>>>> controller versions which suffer from the errata.
>>>>>>>
>>>>>>> Is there a version of controller available which does not have the
>>>>>>> defect? Is there a future plan to fix this?
>>>>>>>
>>>>>>> If any of that is yes, you probably want to handle this with runtime
>>>>>>> detection of version (like done with DWC3_REVISION_XXX macros).
>>>>>>> Sometimes the hardware-read versions themselves are incorrect, so its
>>>>>>> better to introduce a version specific compatible too like
>>>>>>> "cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>>>>>>>
>>>>>>
>>>>>> custom match_ep is used and works with all versions of the gen1
>>>>>> controller. Future (gen2) releases of the controller won’t have such
>>>>>> limitation but there is no plan to change current (gen1) functionality
>>>>>> of the controller.
>>>>>>
>>>>>> I will add comment before cdns3_gadget_match_ep function.
>>>>>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>>>>>> cdns,usb3-1.0.1 compatible.
>>>>>>
>>>>>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>>>>>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>>>>>> I now that I have some changes in controller, and one of them require
>>>>>> some changes in DRD driver. It will be safer to add two separate
>>>>>> version in compatibles.
>>>>>>
>>>>>
>>>>> Pawel, could we have correct register to show controller version? It is
>>>>> better we could version judgement at runtime instead of static compatible.
>>>>
>>>> Agree with detecting IP version at runtime.
>>>>
>>>> But please have some indication of version in compatible string too,
>>>
>>> why? Runtime detection by revision register should be the way to go if
>>> the HW provides it. Why duplicate the information in compatible string?
>>>
>>>> especially since you already know there is going to be another revision
>>>> of hardware. It has the advantage that one can easily grep to see which
>>>> hardware is running current version of controller without having access
>>>> to the hardware itself. Becomes useful later on when its time to
>>>> clean-up unused code when boards become obsolete or for requesting
>>>> testing help.
>>>
>>> This doesn't sound like a very strong argument, actually. Specially when
>>> you consider that, since driver will do revision checking based on
>>> revision register, you already have strings to grep. Moreover, we don't
>>> usually drop support just like that.
>>
>> AFAICS, it is impossible to know just by grep'ing if there is any
>> hardware still supported in kernel and using DWC3_REVISION_194A, for
>> example.
> 
> but why do you even care?

When, for example, its coming in the way of some clean-up I am
attempting to do.

> 
>> If we are never going to drop support for any revision, this does not
>> matter much.
>>
>> Also, once you have the controller supported behind PCI, then I guess
>> you are pretty much tied to having to read hardware revision at runtime.
> 
> that's another argument *for* using runtime detection, not against it.

I know :). I should have stated that in last e-mail itself, I am okay
with just runtime detection.

Thanks,
Sekhar

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

* Re: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14 12:20                     ` Sekhar Nori
@ 2018-12-14 12:30                       ` Felipe Balbi
  2018-12-16 13:31                       ` Pawel Laszczak
  1 sibling, 0 replies; 85+ messages in thread
From: Felipe Balbi @ 2018-12-14 12:30 UTC (permalink / raw)
  To: Sekhar Nori, Peter Chen, pawell
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	adouglas, jbergsagel, nm, sureshp, peter.chen, pjez, kurahul

[-- Attachment #1: Type: text/plain, Size: 1217 bytes --]


Hi,

Sekhar Nori <nsekhar@ti.com> writes:
>>>>> especially since you already know there is going to be another revision
>>>>> of hardware. It has the advantage that one can easily grep to see which
>>>>> hardware is running current version of controller without having access
>>>>> to the hardware itself. Becomes useful later on when its time to
>>>>> clean-up unused code when boards become obsolete or for requesting
>>>>> testing help.
>>>>
>>>> This doesn't sound like a very strong argument, actually. Specially when
>>>> you consider that, since driver will do revision checking based on
>>>> revision register, you already have strings to grep. Moreover, we don't
>>>> usually drop support just like that.
>>>
>>> AFAICS, it is impossible to know just by grep'ing if there is any
>>> hardware still supported in kernel and using DWC3_REVISION_194A, for
>>> example.
>> 
>> but why do you even care?
>
> When, for example, its coming in the way of some clean-up I am
> attempting to do.

can you share one example when a revision check got in the way of a
cleanup? I fail to see a situation when we need to drop support to older
platforms just to clean some code up.

-- 
balbi

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

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

* RE: [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API
  2018-12-14 12:20                     ` Sekhar Nori
  2018-12-14 12:30                       ` Felipe Balbi
@ 2018-12-16 13:31                       ` Pawel Laszczak
  1 sibling, 0 replies; 85+ messages in thread
From: Pawel Laszczak @ 2018-12-16 13:31 UTC (permalink / raw)
  To: Sekhar Nori, Felipe Balbi, Peter Chen
  Cc: rogerq, devicetree, Greg Kroah-Hartman, linux-usb, lkml,
	Alan Douglas, jbergsagel, nm, Suresh Punnoose, peter.chen,
	Pawel Jez, Rahul Kumar

Hi

>
>On 14/12/18 4:56 PM, Felipe Balbi wrote:
>> Hi,
>>
>> Sekhar Nori <nsekhar@ti.com> writes:
>>>>>>>> All this should be part of comments in code along with information about
>>>>>>>> controller versions which suffer from the errata.
>>>>>>>>
>>>>>>>> Is there a version of controller available which does not have the
>>>>>>>> defect? Is there a future plan to fix this?
>>>>>>>>
>>>>>>>> If any of that is yes, you probably want to handle this with runtime
>>>>>>>> detection of version (like done with DWC3_REVISION_XXX macros).
>>>>>>>> Sometimes the hardware-read versions themselves are incorrect, so its
>>>>>>>> better to introduce a version specific compatible too like
>>>>>>>> "cdns,usb-1.0.0" (as hinted to by Rob Herring as well).
>>>>>>>>
>>>>>>>
>>>>>>> custom match_ep is used and works with all versions of the gen1
>>>>>>> controller. Future (gen2) releases of the controller won’t have such
>>>>>>> limitation but there is no plan to change current (gen1) functionality
>>>>>>> of the controller.
>>>>>>>
>>>>>>> I will add comment before cdns3_gadget_match_ep function.
>>>>>>> Also I will change cdns,usb3 to cdns,usb3-1.0.0 and add additional
>>>>>>> cdns,usb3-1.0.1 compatible.
>>>>>>>
>>>>>>> cdns,usb3-1.0.1 will be for current version of controller which I use.
>>>>>>> cdns,usb3-1.0.0 will be for older version - Peter Chan platform.
>>>>>>> I now that I have some changes in controller, and one of them require
>>>>>>> some changes in DRD driver. It will be safer to add two separate
>>>>>>> version in compatibles.
>>>>>>>
>>>>>>
>>>>>> Pawel, could we have correct register to show controller version? It is
>>>>>> better we could version judgement at runtime instead of static compatible.
>>>>>
>>>>> Agree with detecting IP version at runtime.
>>>>>
>>>>> But please have some indication of version in compatible string too,
>>>>
>>>> why? Runtime detection by revision register should be the way to go if
>>>> the HW provides it. Why duplicate the information in compatible string?
>>>>
>>>>> especially since you already know there is going to be another revision
>>>>> of hardware. It has the advantage that one can easily grep to see which
>>>>> hardware is running current version of controller without having access
>>>>> to the hardware itself. Becomes useful later on when its time to
>>>>> clean-up unused code when boards become obsolete or for requesting
>>>>> testing help.
>>>>
>>>> This doesn't sound like a very strong argument, actually. Specially when
>>>> you consider that, since driver will do revision checking based on
>>>> revision register, you already have strings to grep. Moreover, we don't
>>>> usually drop support just like that.
>>>
>>> AFAICS, it is impossible to know just by grep'ing if there is any
>>> hardware still supported in kernel and using DWC3_REVISION_194A, for
>>> example.
>>
>> but why do you even care?
>
>When, for example, its coming in the way of some clean-up I am
>attempting to do.
>
>>
>>> If we are never going to drop support for any revision, this does not
>>> matter much.
>>>
>>> Also, once you have the controller supported behind PCI, then I guess
>>> you are pretty much tied to having to read hardware revision at runtime.
>>
>> that's another argument *for* using runtime detection, not against it.
>
>I know :). I should have stated that in last e-mail itself, I am okay
>with just runtime detection.

I agree with you. Controller has usb_cap6 register that keep 
device controller version. It's not a problem doing such detection
at runtime. I will do it in this way.

But also I will add extra compatible  to dt-binding. Even if this will not be used 
in driver, it informs that there are several versions of controller. 

Thanks,
Pawel

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

end of thread, other threads:[~2018-12-16 13:32 UTC | newest]

Thread overview: 85+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-11-18 10:08 [RFC PATCH v2 00/15] Introduced new Cadence USBSS DRD Driver Pawel Laszczak
2018-11-18 10:08 ` [RFC PATCH v2 01/15] usb:cdns3: add pci to platform driver wrapper Pawel Laszczak
2018-11-23 10:44   ` Roger Quadros
2018-11-18 10:08 ` [RFC PATCH v2 02/15] usb:cdns3: Device side header file Pawel Laszczak
2018-11-30  6:48   ` PETER CHEN
2018-12-02 19:27     ` Pawel Laszczak
2018-11-18 10:08 ` [RFC PATCH v2 03/15] dt-bindings: add binding for USBSS-DRD controller Pawel Laszczak
2018-11-23 10:53   ` Roger Quadros
2018-11-25  7:33     ` Pawel Laszczak
2018-12-04 22:41   ` Rob Herring
2018-12-06 10:26     ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 04/15] usb:cdns3: Driver initialization code Pawel Laszczak
2018-11-23 11:35   ` Roger Quadros
2018-11-25 12:35     ` Pawel Laszczak
2018-12-04  9:09       ` Peter Chen
2018-12-06  7:00         ` Pawel Laszczak
2018-12-04  8:50     ` Peter Chen
2018-12-04 10:46       ` Roger Quadros
2018-12-05  8:57         ` Peter Chen
2018-12-06  9:31         ` Pawel Laszczak
2018-12-05 19:24       ` Pawel Laszczak
2018-12-05 19:42       ` Pawel Laszczak
2018-12-06 10:02         ` Pawel Laszczak
2018-11-30  7:32   ` Peter Chen
2018-12-02 20:34     ` Pawel Laszczak
2018-12-04  7:11       ` Peter Chen
2018-12-05  7:19         ` Pawel Laszczak
2018-12-05  8:55           ` Alan Douglas
2018-12-05  9:07             ` Peter Chen
2018-11-18 10:09 ` [RFC PATCH v2 05/15] usb:cdns3: Added DRD support Pawel Laszczak
2018-11-23 14:51   ` Roger Quadros
2018-11-26  7:23     ` Pawel Laszczak
2018-11-26  8:07       ` Roger Quadros
2018-11-26  8:39         ` Pawel Laszczak
2018-11-26  9:39           ` Roger Quadros
2018-11-26 10:09             ` Pawel Laszczak
2018-11-26 10:15               ` Roger Quadros
2018-11-27 11:29       ` Pawel Laszczak
2018-11-27 12:10         ` Roger Quadros
2018-12-04  9:18   ` Peter Chen
2018-12-06  7:25     ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 06/15] usb:cdns3: Adds Host support Pawel Laszczak
2018-11-23 14:23   ` Roger Quadros
2018-11-26  8:24     ` Pawel Laszczak
2018-11-26  9:50       ` Roger Quadros
2018-11-26 10:17         ` Pawel Laszczak
2018-12-05  8:41     ` Peter Chen
2018-11-18 10:09 ` [RFC PATCH v2 07/15] usb:cdns3: Adds Device mode support - initialization Pawel Laszczak
2018-11-28 11:34   ` Roger Quadros
2018-11-28 11:40     ` Felipe Balbi
2018-11-30  4:20       ` PETER CHEN
2018-11-30  6:29         ` Pawel Laszczak
2018-11-30 14:36     ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 08/15] usb:cdns3: Implements device operations part of the API Pawel Laszczak
2018-11-28 12:22   ` Roger Quadros
2018-12-01 11:11     ` Pawel Laszczak
2018-12-03 10:19       ` Pawel Laszczak
2018-12-10  2:12     ` Peter Chen
2018-12-11 11:26       ` Sekhar Nori
2018-12-11 19:49         ` Pawel Laszczak
2018-12-14  1:34           ` Peter Chen
2018-12-14  6:49             ` Pawel Laszczak
2018-12-14 10:39             ` Sekhar Nori
2018-12-14 10:47               ` Felipe Balbi
2018-12-14 11:13                 ` Sekhar Nori
2018-12-14 11:26                   ` Felipe Balbi
2018-12-14 12:20                     ` Sekhar Nori
2018-12-14 12:30                       ` Felipe Balbi
2018-12-16 13:31                       ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 09/15] usb:cdns3: EpX " Pawel Laszczak
2018-11-28 12:46   ` Roger Quadros
2018-12-01 13:30     ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 10/15] usb:cdns3: Ep0 " Pawel Laszczak
2018-11-28 14:31   ` Roger Quadros
2018-12-02 10:34     ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 11/15] usb:cdns3: Implements ISR functionality Pawel Laszczak
2018-11-28 14:54   ` Roger Quadros
2018-12-02 11:49     ` Pawel Laszczak
2018-12-02 12:52       ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 12/15] usb:cdns3: Adds enumeration related function Pawel Laszczak
2018-11-28 15:50   ` Roger Quadros
2018-12-02 16:39     ` Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 13/15] usb:cdns3: Adds transfer " Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 14/15] usb:cdns3: Adds debugging function Pawel Laszczak
2018-11-18 10:09 ` [RFC PATCH v2 15/15] usb:cdns3: Feature for changing role Pawel Laszczak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).