* [PATCH v2 0/3] Add USB driver for HPE GXP Architecture
@ 2023-09-07 21:05 richard.yu
2023-09-07 21:05 ` [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller richard.yu
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: richard.yu @ 2023-09-07 21:05 UTC (permalink / raw)
To: verdun, nick.hawkins, gregkh, robh+dt, krzysztof.kozlowski+dt,
conor+dt, richard.yu, linux-usb, devicetree, linux-kernel
From: Richard Yu <richard.yu@hpe.com>
The GXP hub controller presents a four ports to host software.
Each port is logically connected to one control endpoint and
four generic endpoints.
---
Changes since v1:
*Renamed binding.
*Removed all unneeded include files.
*Using generic node name, usb-hub.
*Removed of_match_ptr().
*Using helper for both get resource and IO remap.
*Using dev_err_probe() return in probe routine.
*Used sizeof(*...) in the code instead of sizeof(struct...).
*Removed the term "virtual" as it is still a device.
*Removed the downstream port number and generic endpoints
number properties from device tree structure.
Richard Yu (3):
dt-bindings: usb: Add HPE GXP HUB Controller
usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support
MAINTAINERS: add USB HUB support for GXP
.../devicetree/bindings/usb/hpe,gxp-hub.yaml | 53 +
MAINTAINERS | 2 +
drivers/usb/gadget/udc/Kconfig | 10 +
drivers/usb/gadget/udc/Makefile | 1 +
drivers/usb/gadget/udc/gxp-udc.c | 1371 +++++++++++++++++
5 files changed, 1437 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml
create mode 100644 drivers/usb/gadget/udc/gxp-udc.c
--
2.17.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller
2023-09-07 21:05 [PATCH v2 0/3] Add USB driver for HPE GXP Architecture richard.yu
@ 2023-09-07 21:05 ` richard.yu
2023-09-11 14:57 ` Rob Herring
2023-09-07 21:06 ` [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support richard.yu
2023-09-07 21:06 ` [PATCH v2 3/3] MAINTAINERS: add USB HUB support for GXP richard.yu
2 siblings, 1 reply; 7+ messages in thread
From: richard.yu @ 2023-09-07 21:05 UTC (permalink / raw)
To: verdun, nick.hawkins, gregkh, robh+dt, krzysztof.kozlowski+dt,
conor+dt, richard.yu, linux-usb, devicetree, linux-kernel
From: Richard Yu <richard.yu@hpe.com>
Provide access to the two register regions for GXP HUB
controller through the hpe,gxp-hub binding.
Signed-off-by: Richard Yu <richard.yu@hpe.com>
---
v2:
*Removed the term "virtual" as it is still a device.
*Removed the downstream port number and generic endpoints
number properties from device tree structure.
---
.../devicetree/bindings/usb/hpe,gxp-hub.yaml | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml
diff --git a/Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml b/Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml
new file mode 100644
index 000000000000..b3e7bc42d134
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/hpe,gxp-hub.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: HPE GXP USB HUB controller
+
+maintainers:
+ - Nick Hawkins <nick.hawkins@hpe.com>
+ - Richard Yu <richard.yu@hpe.com>
+
+description:
+ The HPE GXP USB HUB Controller implements 1 set of USB
+ register and several sets of device and endpoint registers to support
+ the HUB's downstream USB devices.
+
+properties:
+ compatible:
+ enum:
+ - hpe,gxp-hub
+
+ reg:
+ items:
+ - description: GXP hub (gxphub) controller register set
+ - description: Several sets of Device and Endpoint registers to support
+ the HUB's downstream USB devices.
+
+ reg-names:
+ items:
+ - const: gxphub
+ - const: udc
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ usb-hub@80400800 {
+ compatible = "hpe,gxp-hub";
+ reg = <0x80400800 0x0200>, <0x80401000 0x8000>;
+ reg-names = "gxphub", "udc";
+ interrupts = <13>;
+ interrupt-parent = <&vic1>;
+ };
--
2.17.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support
2023-09-07 21:05 [PATCH v2 0/3] Add USB driver for HPE GXP Architecture richard.yu
2023-09-07 21:05 ` [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller richard.yu
@ 2023-09-07 21:06 ` richard.yu
2023-10-02 12:21 ` Greg KH
2023-09-07 21:06 ` [PATCH v2 3/3] MAINTAINERS: add USB HUB support for GXP richard.yu
2 siblings, 1 reply; 7+ messages in thread
From: richard.yu @ 2023-09-07 21:06 UTC (permalink / raw)
To: verdun, nick.hawkins, gregkh, robh+dt, krzysztof.kozlowski+dt,
conor+dt, richard.yu, linux-usb, devicetree, linux-kernel
From: Richard Yu <richard.yu@hpe.com>
The HPE GXP HUB controller presents a four port EHCI compatible PCI
function to host software. Each port is logically connected to one
control endpoint and four programmable endpoints.
Signed-off-by: Richard Yu <richard.yu@hpe.com>
---
v2:
*Removed all unneeded include files.
*Using generic node name, usb-hub.
*Removed of_match_ptr().
*Using helper for both get resource and IO remap.
*Using dev_err_probe() return in probe routine.
*Used sizeof(*...) in the code instead of sizeof(struct...).
*Removed the downstream port number and generic endpoints
number properties from device tree structure.
---
drivers/usb/gadget/udc/Kconfig | 10 +
drivers/usb/gadget/udc/Makefile | 1 +
drivers/usb/gadget/udc/gxp-udc.c | 1371 ++++++++++++++++++++++++++++++
3 files changed, 1382 insertions(+)
create mode 100644 drivers/usb/gadget/udc/gxp-udc.c
diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 83cae6bb12eb..513c97735c66 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -461,6 +461,16 @@ config USB_ASPEED_UDC
dynamically linked module called "aspeed_udc" and force all
gadget drivers to also be dynamically linked.
+config USB_GXP_UDC
+ bool "GXP HUB Driver"
+ depends on ARCH_HPE_GXP || COMPILE_TEST
+ help
+ Enables HPE GXP HUB driver for HPE GXP. The GXP HUB
+ supports 4 ports. Each port supports 1 control
+ endpoint and 4 programmable endpoints.
+
+ Say "y" to add support for GXP HUB driver
+
source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig"
#
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index ee569f63c74a..63fa262f31c5 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/
obj-$(CONFIG_USB_ASPEED_UDC) += aspeed_udc.o
obj-$(CONFIG_USB_BDC_UDC) += bdc/
obj-$(CONFIG_USB_MAX3420_UDC) += max3420_udc.o
+obj-$(CONFIG_USB_GXP_UDC) += gxp-udc.o
diff --git a/drivers/usb/gadget/udc/gxp-udc.c b/drivers/usb/gadget/udc/gxp-udc.c
new file mode 100644
index 000000000000..c78a9ddae6de
--- /dev/null
+++ b/drivers/usb/gadget/udc/gxp-udc.c
@@ -0,0 +1,1371 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/usb/gadget.h>
+#include <linux/regmap.h>
+
+#define VUHCBASE 0x80400000
+#define FRINDEX 0x0000002c
+
+#define EVGBASE 0x0800
+#define EVGISTAT 0x0000
+#define EVGIEN 0x0004
+#define EVDBG 0x0008
+#define EVFEMAP 0x0010
+#define EVFEMAP_VDEVNUM_MASK 0x07
+#define EVFEMAP_VDEVNUM(val) val
+#define EVFEMAP_EPNUM_MASK 0x70
+#define EVFEMAP_EPNUM(val) ((val) << 4)
+
+#define EVDBASE 0x1000
+#define EVDISTAT 0x0000
+#define EVDISTAT_ALL_EP_INT 0x000000ff
+#define EVDISTAT_EP0_INT 0x00000001
+#define EVDISTAT_EP1_INT 0x00000002
+#define EVDISTAT_EP2_INT 0x00000004
+#define EVDISTAT_EP3_INT 0x00000008
+#define EVDISTAT_EP4_INT 0x00000010
+#define EVDISTAT_EP5_INT 0x00000020
+#define EVDISTAT_EP6_INT 0x00000040
+#define EVDISTAT_EP7_INT 0x00000080
+#define EVDISTAT_SETUP_DAT_RCV 0x00010000
+#define EVDISTAT_PORTEN_CHANGE 0x00100000
+#define EVDISTAT_PORTRST 0x80000000
+
+#define EVDIEN 0x0004
+
+#define EVDISTAT_MASK (EVDISTAT_SETUP_DAT_RCV | EVDISTAT_PORTEN_CHANGE | \
+ EVDISTAT_PORTRST | EVDISTAT_ALL_EP_INT)
+#define EVDIEN_MASK (EVDISTAT_SETUP_DAT_RCV | EVDISTAT_PORTEN_CHANGE | \
+ EVDISTAT_PORTRST | EVDISTAT_EP0_INT)
+
+#define EVDCS 0x0008
+#define EVDCS_CONNECT 0x00000001
+#define EVDCS_PORTENABLESTS 0x01000000
+
+#define EVDADDR 0x000c
+#define EVDSETUPL 0x0010
+#define EVDSETUPH 0x0014
+
+#define EVDEPCC 0x0000
+#define EVDEPCC_EPENABLE 0x00000001
+#define EVDEPCC_CFGINOUT 0x00000002
+#define EVDEPCC_DT_RESET 0x00000004
+#define EVDEPCC_DISARMCMD 0x00000020
+#define EVDEPCC_STALL 0x00000080
+#define EVDEPCC_TXN_PER_UFRAME_MASK 0x00030000
+#define EVDEPCC_TXN_PER_UFRAME(val) ((val) << 16)
+#define EVDEPCC_EP_TYPE_MASK 0x000C0000
+#define EVDEPCC_EP_TYPE(val) ((val) << 18)
+#define EVDEPCC_EP_SIZE_MASK 0x00700000
+#define EVDEPCC_EP_SIZE(val) ((val) << 20)
+
+#define EP_TYPE_CONTROL 0x00
+#define EP_TYPE_BULK 0x01
+#define EP_TYPE_INT 0x02
+#define EP_TYPE_ISOC 0x03
+
+#define EP_IN 0x01
+#define EP_OUT 0x00
+
+#define EVDEPSTS 0x0004
+#define EVDEPSTS_DONE 0x00000001
+#define EVDEPSTS_SHORT 0x00000002
+#define EVDEPSTS_STALLDET 0x00000004
+#define EVDEPSTS_BUFARMED 0x00010000
+
+#define EVDEPEVTEN 0x0008
+#define EVDEPEVTEN_DONE 0x00000001
+#define EVDEPEVTEN_SHORT 0x00000002
+#define EVDEPEVTEN_STALLDET 0x00000004
+#define EVDEPADDR 0x000c
+#define EVDEPLEN 0x0010
+#define EVDEPNTX 0x0014
+
+#define GXP_UDC_MAX_NUM_EP 8
+#define GXP_UDC_MAX_NUM_FLEX_EP 16
+#define GXP_UDC_MAX_NUM_VDEVICE 4
+#define GXP_UDC_MAX_NUM_FLEX_EP_PER_VDEV 7
+
+static bool udcg_init_done;
+
+static const char * const gxp_udc_name[] = {
+ "gxp-udc0", "gxp-udc1", "gxp-udc2", "gxp-udc3",
+ "gxp-udc4", "gxp-udc5", "gxp-udc6", "gxp-udc7"};
+
+static const char * const gxp_udc_ep_name[] = {
+ "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7"};
+
+struct gxp_udc_req {
+ struct usb_request req;
+ struct list_head queue;
+};
+
+struct gxp_udc_ep {
+ void __iomem *base;
+ struct gxp_udc_drvdata *drvdata;
+
+ struct usb_ep ep;
+ struct list_head queue;
+ unsigned char epnum;
+ unsigned char type;
+ bool stall;
+ bool wedged;
+ int is_in;
+};
+
+struct gxp_udc_drvdata {
+ void __iomem *base;
+ struct platform_device *pdev;
+ struct regmap *udcg_map;
+ struct gxp_udc_ep ep[GXP_UDC_MAX_NUM_EP];
+
+ int irq;
+
+ /* sysfs enclosure for the gadget gunk */
+ struct device *port_dev;
+
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+
+ u32 vdevnum;
+ u32 fepnum;
+ u32 devaddr;
+
+ __le16 ep0_data;
+ struct usb_request *ep0_req;
+ bool deffer_set_address;
+
+ /* this lock is used to protect the drvdata structure access */
+ spinlock_t lock;
+};
+
+struct gxp_udcg_drvdata {
+ void __iomem *base;
+ struct platform_device *pdev;
+ void __iomem *udcg_base;
+ struct regmap *udcg_map;
+ struct gxp_udc_ep ep[GXP_UDC_MAX_NUM_EP];
+
+ int irq;
+
+ u32 max_vdevnum;
+ u32 max_fepnum;
+ u32 devaddr;
+ struct gxp_udc_drvdata *udc_drvdata[GXP_UDC_MAX_NUM_VDEVICE];
+
+ /* this lock is used to protect the udcg drvdata structure access */
+ spinlock_t lock;
+};
+
+struct regmap_config regmap_cfg = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+};
+
+static void gxp_udc_out_0byte_status(struct gxp_udc_ep *ep);
+static void gxp_udc_in_0byte_status(struct gxp_udc_ep *ep);
+
+static unsigned char gxp_udc_femap_read(struct gxp_udc_drvdata *drvdata,
+ int fep)
+{
+ unsigned int value;
+ int i = fep & 0x03;
+
+ regmap_read(drvdata->udcg_map, EVFEMAP + (fep & ~0x03), &value);
+ value = (value >> (8 * i)) & 0xff;
+
+ return (unsigned char)value;
+}
+
+static void gxp_udc_femap_write(struct gxp_udc_drvdata *drvdata, int fep,
+ unsigned char value)
+{
+ unsigned int reg;
+ unsigned int tmp = value;
+ int i = fep & 0x03;
+
+ regmap_read(drvdata->udcg_map, EVFEMAP + (fep & ~0x03), ®);
+ tmp = tmp << (8 * i);
+ reg &= ~(0xff << (8 * i));
+ reg |= tmp;
+ regmap_write(drvdata->udcg_map, EVFEMAP + (fep & ~0x03), reg);
+}
+
+static int gxp_udc_start_dma(struct gxp_udc_ep *ep, struct gxp_udc_req *req)
+{
+ writel(req->req.dma, ep->base + EVDEPADDR);
+ writel(req->req.length, ep->base + EVDEPLEN);
+
+ return 0;
+}
+
+static int gxp_udc_done_dma(struct gxp_udc_ep *ep, struct gxp_udc_req *req)
+{
+ u32 len;
+
+ len = readl(ep->base + EVDEPLEN);
+ req->req.actual = req->req.length - len;
+
+ if (len && ep->is_in) {
+ /* remaining data to send, rearm again */
+ writel(len, ep->base + EVDEPLEN);
+ return 0;
+ }
+ return 1;
+}
+
+static u32 evdepcc_ep_type(int value)
+{
+ switch (value) {
+ case USB_ENDPOINT_XFER_CONTROL:
+ /* control */
+ return EP_TYPE_CONTROL;
+
+ case USB_ENDPOINT_XFER_ISOC:
+ /* isochronous */
+ return EP_TYPE_ISOC;
+
+ case USB_ENDPOINT_XFER_BULK:
+ /* bulk */
+ return EP_TYPE_BULK;
+
+ case USB_ENDPOINT_XFER_INT:
+ /* interrupt */
+ return EP_TYPE_INT;
+
+ default:
+ return 0;
+ }
+}
+
+static u32 evdepcc_ep_size(int value)
+{
+ switch (value) {
+ case 8:
+ return 0x00;
+ case 16:
+ return 0x01;
+ case 32:
+ return 0x02;
+ case 64:
+ return 0x03;
+ case 128:
+ return 0x04;
+ case 256:
+ return 0x05;
+ case 512:
+ return 0x06;
+ case 1024:
+ default:
+ return 0x07;
+ }
+}
+
+static int gxp_udc_ep_init(struct gxp_udc_ep *ep, int type, int dir_in,
+ int size)
+{
+ u32 val;
+ u32 tmp;
+
+ ep->is_in = dir_in;
+ ep->type = type;
+ ep->ep.maxpacket = size;
+
+ val = readl(ep->base + EVDEPCC);
+ if (dir_in) {
+ /* to host */
+ val |= EVDEPCC_CFGINOUT;
+ } else {
+ /* to device */
+ val &= ~EVDEPCC_CFGINOUT;
+ }
+
+ tmp = evdepcc_ep_type(type);
+ val &= ~EVDEPCC_EP_TYPE_MASK;
+ val |= EVDEPCC_EP_TYPE(tmp);
+
+ tmp = evdepcc_ep_size(size);
+ val &= ~EVDEPCC_EP_SIZE_MASK;
+ val |= EVDEPCC_EP_SIZE(tmp);
+
+ val &= ~EVDEPCC_TXN_PER_UFRAME_MASK;
+ if (type == USB_ENDPOINT_XFER_INT ||
+ type == USB_ENDPOINT_XFER_ISOC) {
+ val |= EVDEPCC_TXN_PER_UFRAME(1);
+ }
+
+ val |= EVDEPCC_EPENABLE;
+ writel(val, ep->base + EVDEPCC);
+
+ val = readl(ep->base + EVDEPSTS);
+ val &= ~(EVDEPSTS_DONE | EVDEPSTS_SHORT | EVDEPSTS_STALLDET);
+ writel(val, ep->base + EVDEPSTS);
+
+ val = readl(ep->base + EVDEPEVTEN);
+ val |= EVDEPEVTEN_DONE;
+ val |= EVDEPEVTEN_SHORT;
+ val |= EVDEPEVTEN_STALLDET;
+ writel(val, ep->base + EVDEPEVTEN);
+
+ return 0;
+}
+
+static struct usb_request *gxp_udc_ep_alloc_request(struct usb_ep *_ep,
+ gfp_t gfp_flags)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+
+ req = kzalloc(sizeof(*req), gfp_flags);
+ if (!req)
+ return NULL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ pr_debug("%s: ep%d\n", __func__, ep->epnum);
+
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+static void gxp_udc_ep_free_request(struct usb_ep *_ep,
+ struct usb_request *_req)
+{
+ struct gxp_udc_req *req;
+
+ if (!_req)
+ return;
+
+ req = container_of(_req, struct gxp_udc_req, req);
+ WARN_ON(!list_empty(&req->queue));
+ kfree(req);
+}
+
+static int gxp_udc_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+ unsigned long flags;
+ u32 val;
+ int ret;
+
+ if (!_req || !_ep)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ req = container_of(_req, struct gxp_udc_req, req);
+
+ if (!ep->drvdata->driver ||
+ ep->drvdata->gadget.speed == USB_SPEED_UNKNOWN)
+ return -ESHUTDOWN;
+
+ spin_lock_irqsave(&ep->drvdata->lock, flags);
+
+ if (ep->epnum == 0) {
+ val = readl(ep->base + EVDEPCC);
+ ep->is_in = (val & EVDEPCC_CFGINOUT) ? 1 : 0;
+
+ if (!_req->length) {
+ /* ep0 control write transaction no data stage. */
+ _req->status = 0;
+
+ if (_req->complete) {
+ spin_unlock(&ep->drvdata->lock);
+ usb_gadget_giveback_request(_ep, _req);
+ spin_lock(&ep->drvdata->lock);
+ }
+
+ gxp_udc_in_0byte_status(ep);
+ return 0;
+ }
+ }
+
+ ret = usb_gadget_map_request(&ep->drvdata->gadget,
+ &req->req, ep->is_in);
+ if (ret) {
+ dev_dbg(&ep->drvdata->pdev->dev,
+ "usb_gadget_map_request failed ep%d\n", ep->epnum);
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+ return ret;
+ }
+
+ req->req.actual = 0;
+ req->req.status = -EINPROGRESS;
+
+ if (list_empty(&ep->queue)) {
+ list_add_tail(&req->queue, &ep->queue);
+ gxp_udc_start_dma(ep, req);
+ } else {
+ list_add_tail(&req->queue, &ep->queue);
+ }
+
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+
+ return 0;
+}
+
+static int gxp_udc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct gxp_udc_req *req;
+ struct gxp_udc_ep *ep;
+ unsigned long flags;
+
+ if (!_ep || !_req)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ req = container_of(_req, struct gxp_udc_req, req);
+
+ pr_debug("%s: vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+
+ spin_lock_irqsave(&ep->drvdata->lock, flags);
+
+ /* ensure the req is still queued on this ep */
+ list_for_each_entry(req, &ep->queue, queue) {
+ if (&req->req == _req)
+ break;
+ }
+ if (&req->req != _req) {
+ pr_debug("%s: vdev%d ep%d _req is not queued in this ep\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+ return -EINVAL;
+ }
+
+ if (ep->drvdata->gadget.speed == USB_SPEED_UNKNOWN)
+ _req->status = -ESHUTDOWN;
+ else
+ _req->status = -ECONNRESET;
+
+ /* remove req from queue */
+ list_del_init(&req->queue);
+ usb_gadget_unmap_request(&ep->drvdata->gadget, &req->req, ep->is_in);
+ if (req->req.complete) {
+ spin_unlock(&ep->drvdata->lock);
+ usb_gadget_giveback_request(_ep, _req);
+ spin_lock(&ep->drvdata->lock);
+ }
+
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+
+ return 0;
+}
+
+static int gxp_udc_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct gxp_udc_ep *ep;
+ struct gxp_udc_drvdata *drvdata;
+ u32 val;
+
+ if (!_ep)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ drvdata = ep->drvdata;
+
+ pr_debug("%s vdev%d ep%d\n", __func__, drvdata->vdevnum, ep->epnum);
+
+ if (!drvdata->driver || drvdata->gadget.speed == USB_SPEED_UNKNOWN) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d ep enable failed no driver or speed unknown\n",
+ drvdata->vdevnum);
+ return -ESHUTDOWN;
+ }
+
+ if (usb_endpoint_num(desc) > GXP_UDC_MAX_NUM_EP) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d ep enable failed ep%d is out of range(%d)\n",
+ drvdata->vdevnum, usb_endpoint_num(desc),
+ GXP_UDC_MAX_NUM_EP);
+ return -EINVAL;
+ }
+
+ ep->ep.desc = desc;
+
+ ep->stall = false;
+ ep->wedged = false;
+
+ /* setup ep according to the setting of desc */
+ gxp_udc_ep_init(ep, usb_endpoint_type(desc), usb_endpoint_dir_in(desc),
+ usb_endpoint_maxp(desc));
+
+ /* enable ep interrupt */
+ val = readl(drvdata->base + EVDIEN);
+ val |= 1 << ep->epnum;
+ writel(val, drvdata->base + EVDIEN);
+
+ return 0;
+}
+
+static int gxp_udc_ep_disable(struct usb_ep *_ep)
+{
+ struct gxp_udc_ep *ep;
+ struct gxp_udc_req *req;
+ unsigned long flags;
+ u32 val;
+
+ if (!_ep)
+ return -EINVAL;
+
+ if (!_ep->desc)
+ return -EINVAL;
+
+ _ep->desc = NULL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+ ep->stall = false;
+ ep->wedged = false;
+
+ pr_debug("%s vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+ spin_lock_irqsave(&ep->drvdata->lock, flags);
+
+ /* clean queue */
+ while (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct gxp_udc_req, queue);
+ gxp_udc_ep_dequeue(&ep->ep, &req->req);
+ }
+
+ /* disable ep */
+ val = readl(ep->base + EVDEPCC);
+ val &= ~EVDEPCC_EPENABLE;
+ writel(val, ep->base + EVDEPCC);
+
+ /* disable interrupt */
+ val = readl(ep->drvdata->base + EVDIEN);
+ val &= ~(1 << ep->epnum);
+ writel(val, ep->drvdata->base + EVDIEN);
+
+ spin_unlock_irqrestore(&ep->drvdata->lock, flags);
+
+ return 0;
+}
+
+static int gxp_udc_ep_set_halt_and_wedge(struct usb_ep *_ep,
+ int value, int wedge)
+{
+ struct gxp_udc_ep *ep;
+ struct gxp_udc_req *req;
+ u32 val;
+
+ if (!_ep)
+ return -EINVAL;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+
+ pr_debug("vdev%d ep%d gxp_udc_ep_set_halt(%d)\n",
+ ep->drvdata->vdevnum, ep->epnum, value);
+
+ val = readl(ep->base + EVDEPCC);
+
+ /* value = 1, halt on endpoint. 0, clear halt */
+ if (value) {
+ ep->stall = true;
+ if (wedge)
+ ep->wedged = true;
+
+ val |= EVDEPCC_STALL;
+ writel(val, ep->base + EVDEPCC);
+ } else {
+ ep->stall = false;
+ ep->wedged = false;
+
+ val &= ~EVDEPCC_STALL;
+ val |= EVDEPCC_DT_RESET;
+ writel(val, ep->base + EVDEPCC);
+
+ /*
+ * resume dma if it is in endpoint and there is
+ * request in queue when clear halt
+ */
+ if (ep->is_in && !list_empty(&ep->queue) && !value) {
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req, queue);
+ if (req)
+ gxp_udc_start_dma(ep, req);
+ }
+ }
+
+ return 0;
+}
+
+static int gxp_udc_ep_set_halt(struct usb_ep *_ep, int value)
+{
+ struct gxp_udc_ep *ep;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+
+ pr_debug("%s: vdev%d ep%d value:%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum, value);
+ return gxp_udc_ep_set_halt_and_wedge(_ep, value, 0);
+}
+
+static int gxp_udc_ep_set_wedge(struct usb_ep *_ep)
+{
+ struct gxp_udc_ep *ep;
+
+ ep = container_of(_ep, struct gxp_udc_ep, ep);
+
+ pr_debug("%s: vdev%d ep%d\n",
+ __func__, ep->drvdata->vdevnum, ep->epnum);
+ return gxp_udc_ep_set_halt_and_wedge(_ep, 1, 1);
+}
+
+static const struct usb_ep_ops gxp_udc_ep_ops = {
+ .enable = gxp_udc_ep_enable,
+ .disable = gxp_udc_ep_disable,
+
+ .alloc_request = gxp_udc_ep_alloc_request,
+ .free_request = gxp_udc_ep_free_request,
+
+ .queue = gxp_udc_ep_queue,
+ .dequeue = gxp_udc_ep_dequeue,
+
+ .set_halt = gxp_udc_ep_set_halt,
+ .set_wedge = gxp_udc_ep_set_wedge,
+};
+
+static int gxp_udc_connect(struct gxp_udc_drvdata *drvdata)
+{
+ unsigned int val;
+ unsigned char femap;
+ int i;
+ int j;
+
+ /* clear event before enable int */
+ val = readl(drvdata->base + EVDISTAT);
+ val |= EVDISTAT_MASK;
+ val |= (EVDISTAT_SETUP_DAT_RCV | EVDISTAT_PORTEN_CHANGE | EVDISTAT_PORTRST);
+ writel(val, drvdata->base + EVDISTAT);
+
+ /* ep0 init */
+ gxp_udc_ep_init(&drvdata->ep[0], USB_ENDPOINT_XFER_CONTROL, EP_IN, 64);
+
+ /* enable interrupt */
+ val = readl(drvdata->base + EVDIEN);
+ val |= EVDIEN_MASK;
+ writel(val, drvdata->base + EVDIEN);
+
+ /* enable vdevice interrupt */
+ regmap_update_bits(drvdata->udcg_map, EVGIEN, BIT(drvdata->vdevnum),
+ BIT(drvdata->vdevnum));
+
+ /* initial flex ep */
+ for (j = 1; j < drvdata->fepnum + 1; j++) {
+ for (i = 0; i < GXP_UDC_MAX_NUM_FLEX_EP; i++) {
+ femap = gxp_udc_femap_read(drvdata, i);
+ /* if the flex ep is free (ep==0) */
+ if (!(femap & EVFEMAP_EPNUM_MASK)) {
+ femap &= ~EVFEMAP_EPNUM_MASK;
+ femap |= EVFEMAP_EPNUM(j);
+ femap &= ~EVFEMAP_VDEVNUM_MASK;
+ femap |= EVFEMAP_VDEVNUM(drvdata->vdevnum);
+ gxp_udc_femap_write(drvdata, i, femap);
+ break;
+ }
+ }
+ if (i == GXP_UDC_MAX_NUM_FLEX_EP) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d cannot find a free flex ep\n",
+ drvdata->vdevnum);
+ return -ENODEV;
+ }
+ }
+
+ /* set connect bit, EVDCS */
+ val = readl(drvdata->base + EVDCS);
+
+ val |= EVDCS_CONNECT;
+ writel(val, drvdata->base + EVDCS);
+
+ return 0;
+}
+
+static int gxp_udc_disconnect(struct gxp_udc_drvdata *drvdata)
+{
+ unsigned int val;
+ unsigned char femap;
+ int i;
+
+ /* disable interrupt */
+ val = readl(drvdata->base + EVDIEN);
+ val &= ~EVDIEN_MASK;
+ writel(val, drvdata->base + EVDIEN);
+
+ /* disable vdevice interrupt */
+ regmap_update_bits(drvdata->udcg_map, EVGIEN, BIT(drvdata->vdevnum), 0);
+
+ /* clear connect bit, EVDCS */
+ val = readl(drvdata->base + EVDCS);
+ val &= ~EVDCS_CONNECT;
+ writel(val, drvdata->base + EVDCS);
+
+ /* free flex ep */
+ for (i = 0; i < GXP_UDC_MAX_NUM_FLEX_EP; i++) {
+ femap = gxp_udc_femap_read(drvdata, i);
+ if (EVFEMAP_VDEVNUM(drvdata->vdevnum) ==
+ (femap & EVFEMAP_VDEVNUM_MASK)) {
+ gxp_udc_femap_write(drvdata, i, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int gxp_udc_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct gxp_udc_drvdata *drvdata;
+
+ if (!gadget)
+ return -ENODEV;
+
+ drvdata = container_of(gadget, struct gxp_udc_drvdata, gadget);
+ spin_lock(&drvdata->lock);
+
+ if (drvdata->driver) {
+ dev_err(&drvdata->pdev->dev,
+ "vdev%d start failed driver!=NULL\n", drvdata->vdevnum);
+
+ spin_unlock(&drvdata->lock);
+ return -EBUSY;
+ }
+
+ drvdata->deffer_set_address = false;
+
+ /* hook up the driver */
+ driver->driver.bus = NULL;
+ drvdata->driver = driver;
+
+ gxp_udc_connect(drvdata);
+
+ spin_unlock(&drvdata->lock);
+ return 0;
+}
+
+static int gxp_udc_stop(struct usb_gadget *gadget)
+{
+ struct gxp_udc_drvdata *drvdata;
+
+ if (!gadget)
+ return -ENODEV;
+
+ drvdata = container_of(gadget, struct gxp_udc_drvdata, gadget);
+ spin_lock(&drvdata->lock);
+
+ gxp_udc_disconnect(drvdata);
+ drvdata->gadget.speed = USB_SPEED_UNKNOWN;
+ drvdata->driver = NULL;
+
+ spin_unlock(&drvdata->lock);
+ return 0;
+}
+
+static const struct usb_gadget_ops gxp_udc_gadget_ops = {
+ .udc_start = gxp_udc_start,
+ .udc_stop = gxp_udc_stop,
+};
+
+static void gxp_udc_out_0byte_status(struct gxp_udc_ep *ep)
+{
+ u32 evdepcc;
+
+ /* out */
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc &= ~EVDEPCC_CFGINOUT;
+ writel(evdepcc, ep->base + EVDEPCC);
+
+ /* 0 len */
+ writel(0, ep->base + EVDEPLEN);
+}
+
+static void gxp_udc_in_0byte_status(struct gxp_udc_ep *ep)
+{
+ u32 evdepcc;
+
+ /* in */
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc |= EVDEPCC_CFGINOUT;
+ writel(evdepcc, ep->base + EVDEPCC);
+
+ /* 0 len */
+ writel(0, ep->base + EVDEPLEN);
+}
+
+static int gxp_udc_ep_nuke(struct gxp_udc_ep *ep)
+{
+ u32 evdepcc;
+ struct gxp_udc_req *req;
+
+ if (!ep)
+ return -EINVAL;
+
+ /* disarm dma */
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc |= EVDEPCC_DISARMCMD;
+ writel(evdepcc, ep->base + EVDEPCC);
+
+ while (!list_empty(&ep->queue)) {
+ req = list_first_entry(&ep->queue, struct gxp_udc_req, queue);
+ gxp_udc_ep_dequeue(&ep->ep, &req->req);
+ }
+
+ return 0;
+}
+
+static int gxp_udc_set_feature(struct gxp_udc_drvdata *drvdata,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct gxp_udc_ep *ep;
+
+ if (ctrl->bRequestType == USB_RECIP_ENDPOINT) {
+ ep = &drvdata->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK];
+ if (le16_to_cpu(ctrl->wValue) == USB_ENDPOINT_HALT) {
+ gxp_udc_ep_set_halt(&ep->ep, 1);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int gxp_udc_clear_feature(struct gxp_udc_drvdata *drvdata,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct gxp_udc_ep *ep;
+
+ if (ctrl->bRequestType == USB_RECIP_ENDPOINT) {
+ ep = &drvdata->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK];
+ if (le16_to_cpu(ctrl->wValue) == USB_ENDPOINT_HALT) {
+ if (ep->wedged)
+ gxp_udc_ep_set_halt(&ep->ep, 0);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int gxp_udc_get_status(struct gxp_udc_drvdata *drvdata,
+ struct usb_ctrlrequest *ctrl)
+{
+ int ret;
+ u32 evdepcc;
+
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ /* fill the req to send */
+ drvdata->ep0_data = 1;
+ drvdata->ep0_req->length = 2;
+ drvdata->ep0_req->buf = &drvdata->ep0_data;
+ gxp_udc_ep_queue(&drvdata->ep[0].ep, drvdata->ep0_req,
+ GFP_KERNEL);
+ ret = 1;
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ evdepcc = readl(drvdata->ep[ctrl->wIndex &
+ USB_ENDPOINT_NUMBER_MASK].base + EVDEPCC);
+ /* fill the req to send */
+ if (evdepcc & EVDEPCC_STALL)
+ drvdata->ep0_data = 1;
+ else
+ drvdata->ep0_data = 0;
+ drvdata->ep0_req->length = 2;
+ drvdata->ep0_req->buf = &drvdata->ep0_data;
+ gxp_udc_ep_queue(&drvdata->ep[0].ep, drvdata->ep0_req,
+ GFP_KERNEL);
+ ret = 1;
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void gxp_udc_handle_ep0_int(struct gxp_udc_ep *ep)
+{
+ struct gxp_udc_drvdata *drvdata = ep->drvdata;
+ struct gxp_udc_req *req;
+ u32 evdepsts;
+ u32 evdepcc;
+
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepsts = readl(ep->base + EVDEPSTS);
+ writel(evdepsts, ep->base + EVDEPSTS); /* write 1 to clear status */
+
+ if (evdepsts & EVDEPSTS_STALLDET) {
+ pr_debug("ep%d get stalldet int\n", ep->epnum);
+ evdepcc &= ~EVDEPCC_STALL;
+ evdepcc |= EVDEPCC_DT_RESET;
+ writel(evdepcc, ep->base + EVDEPCC);
+ }
+
+ if (evdepsts & EVDEPSTS_DONE || evdepsts & EVDEPSTS_SHORT) {
+ if (drvdata->deffer_set_address) {
+ writel(drvdata->devaddr, drvdata->base + EVDADDR);
+ drvdata->deffer_set_address = false;
+ } else if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req, queue);
+ if (gxp_udc_done_dma(ep, req)) {
+ /* req is done */
+ list_del_init(&req->queue);
+ usb_gadget_unmap_request(&ep->drvdata->gadget,
+ &req->req, ep->is_in);
+ if (req->req.complete) {
+ req->req.status = 0;
+ usb_gadget_giveback_request(&ep->ep,
+ &req->req);
+ }
+
+ if (!list_empty(&ep->queue)) {
+ /* process next req in queue */
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req,
+ queue);
+ gxp_udc_start_dma(ep, req);
+ pr_debug("ep0 queue is not empty after done a req!!\n");
+ } else {
+ /* last req completed -> status stage */
+ if (evdepcc & EVDEPCC_CFGINOUT) {
+ /* data in -> out status */
+ gxp_udc_out_0byte_status(ep);
+ } else {
+ /* data out ->in status */
+ gxp_udc_in_0byte_status(ep);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void gxp_udc_handle_ep_int(struct gxp_udc_ep *ep)
+{
+ struct gxp_udc_req *req;
+ u32 evdepsts;
+ u32 evdepcc;
+
+ evdepsts = readl(ep->base + EVDEPSTS);
+ writel(evdepsts, ep->base + EVDEPSTS); /* write 1 to clear status */
+
+ if (evdepsts & EVDEPSTS_STALLDET) {
+ pr_debug("ep%d get stalldet int\n", ep->epnum);
+ evdepcc = readl(ep->base + EVDEPCC);
+ evdepcc &= ~EVDEPCC_STALL;
+ evdepcc |= EVDEPCC_DT_RESET;
+ writel(evdepcc, ep->base + EVDEPCC);
+ }
+
+ if (evdepsts & EVDEPSTS_DONE || evdepsts & EVDEPSTS_SHORT) {
+ if (evdepsts & EVDEPSTS_SHORT)
+ pr_debug("ep%d get short int\n", ep->epnum);
+ if (evdepsts & EVDEPSTS_DONE)
+ pr_debug("ep%d get done int\n", ep->epnum);
+
+ if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req, queue);
+ if (gxp_udc_done_dma(ep, req)) {
+ /* req is done */
+ list_del_init(&req->queue);
+ usb_gadget_unmap_request(&ep->drvdata->gadget,
+ &req->req, ep->is_in);
+ if (req->req.complete) {
+ req->req.status = 0;
+ usb_gadget_giveback_request(&ep->ep,
+ &req->req);
+ }
+
+ if (!list_empty(&ep->queue)) {
+ /* process next req in queue */
+ req = list_entry(ep->queue.next,
+ struct gxp_udc_req,
+ queue);
+ gxp_udc_start_dma(ep, req);
+ }
+ }
+ }
+ }
+}
+
+static void gxp_udc_handle_setup_data_rcv(struct gxp_udc_drvdata *drvdata)
+{
+ struct usb_ctrlrequest ctrl = {0};
+ u32 *ptr;
+ u32 val;
+ int handled = 0;
+ int ret;
+
+ ptr = (u32 *)&ctrl;
+
+ ptr[0] = readl(drvdata->base + EVDSETUPL);
+ ptr[1] = readl(drvdata->base + EVDSETUPH);
+
+ if ((ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ switch (ctrl.bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ /*
+ * this request is unique in that the device does
+ * not set its address until after the completion
+ * of the status stage.(ref: usb nutshell)
+ */
+ drvdata->devaddr = ctrl.wValue & 0x7f;
+ drvdata->deffer_set_address = true;
+ gxp_udc_in_0byte_status(&drvdata->ep[0]);
+ handled = 1;
+ break;
+
+ case USB_REQ_SET_FEATURE:
+ handled = gxp_udc_set_feature(drvdata, &ctrl);
+ break;
+
+ case USB_REQ_CLEAR_FEATURE:
+ handled = gxp_udc_clear_feature(drvdata, &ctrl);
+ break;
+
+ case USB_REQ_GET_STATUS:
+ handled = gxp_udc_get_status(drvdata, &ctrl);
+ break;
+
+ default:
+ handled = 0;
+ break;
+ }
+ }
+
+ /*
+ * pass the request to upper layer driver code(composite.c)
+ * if it is not handled by the code above.
+ */
+ if (drvdata->driver && !handled) {
+ ret = drvdata->driver->setup(&drvdata->gadget, &ctrl);
+ if (ret < 0) {
+ dev_err(&drvdata->pdev->dev,
+ "setup failed, return %d\n", ret);
+ /* set ep0 to stall */
+ val = readl(drvdata->ep[0].base + EVDEPCC);
+ val |= EVDEPCC_STALL;
+ writel(val, drvdata->ep[0].base + EVDEPCC);
+ return;
+ }
+ }
+}
+
+static irqreturn_t gxp_udc_irq(int irq, void *_drvdata)
+{
+ struct gxp_udc_drvdata *drvdata = _drvdata;
+
+ u32 evgistat;
+ u32 evdistat;
+ u32 evdcs;
+ u32 i;
+
+ regmap_read(drvdata->udcg_map, EVGISTAT, &evgistat);
+ if (evgistat & (1 << drvdata->vdevnum)) {
+ evdistat = readl(drvdata->base + EVDISTAT);
+ while (evdistat & EVDISTAT_MASK) {
+ pr_debug("vdev%d get int = 0x%08x\n",
+ drvdata->vdevnum, evdistat);
+
+ if (evdistat & EVDISTAT_PORTEN_CHANGE) {
+ evdcs = readl(drvdata->base + EVDCS);
+ drvdata->gadget.speed =
+ (evdcs & EVDCS_PORTENABLESTS) ?
+ USB_SPEED_HIGH : USB_SPEED_UNKNOWN;
+
+ pr_debug("vdev%d get port enable change int speed = %d\n",
+ drvdata->vdevnum,
+ drvdata->gadget.speed);
+
+ /* write 1 to clear */
+ writel(EVDISTAT_PORTEN_CHANGE,
+ drvdata->base + EVDISTAT);
+ }
+
+ if (evdistat & EVDISTAT_PORTRST) {
+ pr_debug("vdev%d get port rst int\n",
+ drvdata->vdevnum);
+ writel(0, drvdata->base + EVDADDR);
+
+ for (i = 0; i < drvdata->fepnum + 1; i++)
+ gxp_udc_ep_nuke(&drvdata->ep[i]);
+
+ usb_gadget_udc_reset(&drvdata->gadget,
+ drvdata->driver);
+
+ /* write 1 to clear */
+ writel(EVDISTAT_PORTRST,
+ drvdata->base + EVDISTAT);
+ }
+
+ if (evdistat & EVDISTAT_SETUP_DAT_RCV) {
+ pr_debug("vdev%d get setup data rcv int\n",
+ drvdata->vdevnum);
+ gxp_udc_handle_setup_data_rcv(drvdata);
+ /* write 1 to clear */
+ writel(EVDISTAT_SETUP_DAT_RCV,
+ drvdata->base + EVDISTAT);
+ }
+ if (evdistat & 0x01)
+ gxp_udc_handle_ep0_int(&drvdata->ep[0]);
+
+ for (i = 1; i < GXP_UDC_MAX_NUM_EP; i++)
+ if (evdistat & (1 << i))
+ gxp_udc_handle_ep_int(&drvdata->ep[i]);
+
+ evdistat = readl(drvdata->base + EVDISTAT);
+ }
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int gxp_udc_init(struct gxp_udc_drvdata *drvdata)
+{
+ int i;
+ struct gxp_udc_ep *ep;
+
+ /* Disable device interrupt */
+ writel(0, drvdata->base + EVDIEN);
+
+ drvdata->gadget.max_speed = USB_SPEED_HIGH;
+ drvdata->gadget.speed = USB_SPEED_UNKNOWN;
+ drvdata->gadget.ops = &gxp_udc_gadget_ops;
+ drvdata->gadget.name = gxp_udc_name[drvdata->vdevnum];
+ drvdata->gadget.is_otg = 0;
+ drvdata->gadget.is_a_peripheral = 0;
+
+ drvdata->gadget.ep0 = &drvdata->ep[0].ep;
+ drvdata->ep0_req = gxp_udc_ep_alloc_request(&drvdata->ep[0].ep,
+ GFP_KERNEL);
+
+ INIT_LIST_HEAD(&drvdata->gadget.ep_list);
+
+ for (i = 0; i < drvdata->fepnum + 1; i++) {
+ ep = &drvdata->ep[i];
+
+ ep->drvdata = drvdata;
+
+ INIT_LIST_HEAD(&ep->queue);
+ INIT_LIST_HEAD(&ep->ep.ep_list);
+ if (i > 0)
+ list_add_tail(&ep->ep.ep_list,
+ &drvdata->gadget.ep_list);
+
+ usb_ep_set_maxpacket_limit(&ep->ep, (i > 0) ? 512 : 64);
+ ep->epnum = i;
+ ep->ep.name = gxp_udc_ep_name[i];
+ ep->ep.ops = &gxp_udc_ep_ops;
+ ep->base = drvdata->base + 0x20 * (i + 1);
+ if (i == 0) {
+ ep->ep.caps.type_control = true;
+ } else {
+ ep->ep.caps.type_iso = true;
+ ep->ep.caps.type_bulk = true;
+ ep->ep.caps.type_int = true;
+ }
+
+ ep->ep.caps.dir_in = true;
+ ep->ep.caps.dir_out = true;
+ }
+
+ return 0;
+}
+
+static void gxp_udcg_init(struct gxp_udcg_drvdata *drvdata)
+{
+ int i;
+
+ if (!udcg_init_done) {
+ /* disable interrupt */
+ regmap_write(drvdata->udcg_map, EVGIEN, 0);
+ writel(0, drvdata->base + EVGIEN);
+
+ /* disable all flex ep map */
+ for (i = 0; i < GXP_UDC_MAX_NUM_FLEX_EP; i += 4)
+ regmap_write(drvdata->udcg_map, EVFEMAP + i, 0);
+
+ udcg_init_done = true;
+ }
+}
+
+static void gxp_udc_dev_release(struct device *dev)
+{
+ kfree(dev);
+}
+
+static int gxp_udc_init_dev(struct gxp_udcg_drvdata *udcg, unsigned int idx)
+{
+ struct gxp_udc_drvdata *drvdata;
+ struct device *parent = &udcg->pdev->dev;
+ int rc;
+
+ drvdata = devm_kzalloc(parent, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ udcg->udc_drvdata[idx] = drvdata;
+ drvdata->vdevnum = idx;
+ drvdata->base = udcg->base + 0x1000 * idx;
+ drvdata->udcg_map = udcg->udcg_map;
+ drvdata->irq = udcg->irq;
+
+ /*
+ * we setup USB device can have up to 4 endpoints besides control
+ * endpoint 0.
+ */
+ drvdata->fepnum = min_t(u32, udcg->max_fepnum, 4);
+
+ drvdata->vdevnum = idx;
+
+ /*
+ * The UDC core really needs us to have separate and uniquely
+ * named "parent" devices for each port so we create a sub device
+ * here for that purpose
+ */
+ drvdata->port_dev = kzalloc(sizeof(*drvdata->port_dev), GFP_KERNEL);
+ if (!drvdata->port_dev) {
+ rc = -ENOMEM;
+ goto fail_alloc;
+ }
+ device_initialize(drvdata->port_dev);
+ drvdata->port_dev->release = gxp_udc_dev_release;
+ drvdata->port_dev->parent = parent;
+ dev_set_name(drvdata->port_dev, "%s:p%d", dev_name(parent), idx + 1);
+
+ /* DMA setting */
+ drvdata->port_dev->dma_mask = parent->dma_mask;
+ drvdata->port_dev->coherent_dma_mask = parent->coherent_dma_mask;
+ drvdata->port_dev->bus_dma_limit = parent->bus_dma_limit;
+ drvdata->port_dev->dma_range_map = parent->dma_range_map;
+ drvdata->port_dev->dma_parms = parent->dma_parms;
+ drvdata->port_dev->dma_pools = parent->dma_pools;
+
+ rc = device_add(drvdata->port_dev);
+ if (rc)
+ goto fail_add;
+
+ /* Populate gadget */
+ gxp_udc_init(drvdata);
+
+ rc = usb_add_gadget_udc(drvdata->port_dev, &drvdata->gadget);
+ if (rc != 0) {
+ dev_err(drvdata->port_dev, "add gadget failed\n");
+ goto fail_udc;
+ }
+ rc = devm_request_irq(drvdata->port_dev,
+ drvdata->irq,
+ gxp_udc_irq,
+ IRQF_SHARED,
+ gxp_udc_name[drvdata->vdevnum],
+ drvdata);
+
+ if (rc < 0) {
+ dev_err(drvdata->port_dev, "irq request failed\n");
+ goto fail_udc;
+ }
+
+ return 0;
+
+ /* ran code to simulate these three error exit, no double free */
+fail_udc:
+ device_del(drvdata->port_dev);
+fail_add:
+ put_device(drvdata->port_dev);
+fail_alloc:
+ devm_kfree(parent, drvdata);
+
+ return rc;
+}
+
+static int gxp_udcg_probe(struct platform_device *pdev)
+{
+ struct gxp_udcg_drvdata *drvdata;
+ int ret;
+ u32 i;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(struct gxp_udcg_drvdata),
+ GFP_KERNEL);
+ platform_set_drvdata(pdev, drvdata);
+
+ /* The device structure is part of platform device structure */
+ /* Saving the platform device points here, Thus, we can get to the */
+ /* parent device while creating the child device in gxp_udc_init_dev() */
+ drvdata->pdev = pdev;
+
+ spin_lock_init(&drvdata->lock);
+
+ drvdata->udcg_base = devm_platform_ioremap_resource_byname(pdev, "gxphub");
+ if (IS_ERR(drvdata->udcg_base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->udcg_base),
+ "failed to get gxphub resource\n");
+
+ drvdata->udcg_map = devm_regmap_init_mmio(&pdev->dev, drvdata->udcg_base, ®map_cfg);
+
+ if (IS_ERR(drvdata->udcg_map))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->udcg_map),
+ "failed to map gxphub regs\n");
+
+ drvdata->base = devm_platform_ioremap_resource_byname(pdev, "udc");
+ if (IS_ERR(drvdata->base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(drvdata->base),
+ "failed to get udc resource\n");
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "unable to obtain IRQ number\n");
+ drvdata->irq = ret;
+
+ drvdata->max_vdevnum = GXP_UDC_MAX_NUM_VDEVICE;
+
+ drvdata->max_fepnum = GXP_UDC_MAX_NUM_FLEX_EP;
+
+ gxp_udcg_init(drvdata);
+
+ /* Init child devices */
+ ret = 0;
+ for (i = 0; i < drvdata->max_vdevnum && ret == 0; i++)
+ ret = gxp_udc_init_dev(drvdata, i);
+
+ if (ret != 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "fail to create child port device\n");
+
+ return 0;
+}
+
+static const struct of_device_id gxp_udc_of_match[] = {
+ { .compatible = "hpe,gxp-hub" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gxp_udc_of_match);
+
+static struct platform_driver gxp_udc_driver = {
+ .driver = {
+ .name = "gxp-hub",
+ .of_match_table = gxp_udc_of_match,
+ },
+ .probe = gxp_udcg_probe,
+};
+module_platform_driver(gxp_udc_driver);
+
+MODULE_AUTHOR("Richard Yu <richard.yu@hpe.com>");
+MODULE_DESCRIPTION("HPE GXP HUB Driver");
+MODULE_LICENSE("GPL");
--
2.17.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 3/3] MAINTAINERS: add USB HUB support for GXP
2023-09-07 21:05 [PATCH v2 0/3] Add USB driver for HPE GXP Architecture richard.yu
2023-09-07 21:05 ` [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller richard.yu
2023-09-07 21:06 ` [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support richard.yu
@ 2023-09-07 21:06 ` richard.yu
2 siblings, 0 replies; 7+ messages in thread
From: richard.yu @ 2023-09-07 21:06 UTC (permalink / raw)
To: verdun, nick.hawkins, gregkh, robh+dt, krzysztof.kozlowski+dt,
conor+dt, richard.yu, linux-usb, devicetree, linux-kernel
From: Richard Yu <richard.yu@hpe.com>
Add the usb hub driver and dt-bindinng documents.
Signed-off-by: Richard Yu <richard.yu@hpe.com>
---
v2:
*Changed file to hpe,gxp-hub.yaml
---
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 27ef11624748..e16b8b39d47f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2245,6 +2245,7 @@ F: Documentation/devicetree/bindings/hwmon/hpe,gxp-fan-ctrl.yaml
F: Documentation/devicetree/bindings/i2c/hpe,gxp-i2c.yaml
F: Documentation/devicetree/bindings/spi/hpe,gxp-spifi.yaml
F: Documentation/devicetree/bindings/timer/hpe,gxp-timer.yaml
+F: Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml
F: Documentation/hwmon/gxp-fan-ctrl.rst
F: arch/arm/boot/dts/hpe-bmc*
F: arch/arm/boot/dts/hpe-gxp*
@@ -2253,6 +2254,7 @@ F: drivers/clocksource/timer-gxp.c
F: drivers/hwmon/gxp-fan-ctrl.c
F: drivers/i2c/busses/i2c-gxp.c
F: drivers/spi/spi-gxp.c
+F: drivers/usb/gadget/udc/gxp-udc.c
F: drivers/watchdog/gxp-wdt.c
ARM/IGEP MACHINE SUPPORT
--
2.17.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller
2023-09-07 21:05 ` [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller richard.yu
@ 2023-09-11 14:57 ` Rob Herring
0 siblings, 0 replies; 7+ messages in thread
From: Rob Herring @ 2023-09-11 14:57 UTC (permalink / raw)
To: richard.yu
Cc: nick.hawkins, verdun, linux-usb, gregkh, robh+dt, linux-kernel,
conor+dt, devicetree, krzysztof.kozlowski+dt
On Thu, 07 Sep 2023 16:05:59 -0500, richard.yu@hpe.com wrote:
> From: Richard Yu <richard.yu@hpe.com>
>
> Provide access to the two register regions for GXP HUB
> controller through the hpe,gxp-hub binding.
>
> Signed-off-by: Richard Yu <richard.yu@hpe.com>
>
> ---
>
> v2:
> *Removed the term "virtual" as it is still a device.
> *Removed the downstream port number and generic endpoints
> number properties from device tree structure.
> ---
> .../devicetree/bindings/usb/hpe,gxp-hub.yaml | 53 +++++++++++++++++++
> 1 file changed, 53 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/usb/hpe,gxp-hub.yaml
>
Reviewed-by: Rob Herring <robh@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support
2023-09-07 21:06 ` [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support richard.yu
@ 2023-10-02 12:21 ` Greg KH
2023-10-25 22:14 ` Yu, Richard
0 siblings, 1 reply; 7+ messages in thread
From: Greg KH @ 2023-10-02 12:21 UTC (permalink / raw)
To: richard.yu
Cc: verdun, nick.hawkins, robh+dt, krzysztof.kozlowski+dt, conor+dt,
linux-usb, devicetree, linux-kernel
On Thu, Sep 07, 2023 at 04:06:00PM -0500, richard.yu@hpe.com wrote:
> +struct gxp_udc_drvdata {
> + void __iomem *base;
> + struct platform_device *pdev;
> + struct regmap *udcg_map;
> + struct gxp_udc_ep ep[GXP_UDC_MAX_NUM_EP];
> +
> + int irq;
> +
> + /* sysfs enclosure for the gadget gunk */
> + struct device *port_dev;
A "raw" struct device? That's not ok. It's also going to break things,
how was this tested? What does it look like in sysfs with this device?
> + /*
> + * The UDC core really needs us to have separate and uniquely
> + * named "parent" devices for each port so we create a sub device
> + * here for that purpose
> + */
> + drvdata->port_dev = kzalloc(sizeof(*drvdata->port_dev), GFP_KERNEL);
> + if (!drvdata->port_dev) {
> + rc = -ENOMEM;
> + goto fail_alloc;
> + }
> + device_initialize(drvdata->port_dev);
> + drvdata->port_dev->release = gxp_udc_dev_release;
> + drvdata->port_dev->parent = parent;
> + dev_set_name(drvdata->port_dev, "%s:p%d", dev_name(parent), idx + 1);
> +
> + /* DMA setting */
> + drvdata->port_dev->dma_mask = parent->dma_mask;
> + drvdata->port_dev->coherent_dma_mask = parent->coherent_dma_mask;
> + drvdata->port_dev->bus_dma_limit = parent->bus_dma_limit;
> + drvdata->port_dev->dma_range_map = parent->dma_range_map;
> + drvdata->port_dev->dma_parms = parent->dma_parms;
> + drvdata->port_dev->dma_pools = parent->dma_pools;
> +
> + rc = device_add(drvdata->port_dev);
So you createad a "raw" device that does not belong to any bus or type
and add it to sysfs? Why? Shouldn't it be a "virtual" device if you
really want/need one?
> + if (rc)
> + goto fail_add;
> +
> + /* Populate gadget */
> + gxp_udc_init(drvdata);
> +
> + rc = usb_add_gadget_udc(drvdata->port_dev, &drvdata->gadget);
> + if (rc != 0) {
> + dev_err(drvdata->port_dev, "add gadget failed\n");
> + goto fail_udc;
> + }
> + rc = devm_request_irq(drvdata->port_dev,
> + drvdata->irq,
> + gxp_udc_irq,
> + IRQF_SHARED,
> + gxp_udc_name[drvdata->vdevnum],
> + drvdata);
devm_request_irq() is _very_ tricky, are you _SURE_ you got it right
here? Why do you need to use it?
> + if (rc < 0) {
> + dev_err(drvdata->port_dev, "irq request failed\n");
> + goto fail_udc;
> + }
> +
> + return 0;
> +
> + /* ran code to simulate these three error exit, no double free */
What does this comment mean?
> +fail_udc:
> + device_del(drvdata->port_dev);
> +fail_add:
> + put_device(drvdata->port_dev);
> +fail_alloc:
> + devm_kfree(parent, drvdata);
> +
> + return rc;
> +}
Where is the device removed from the system when done?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support
2023-10-02 12:21 ` Greg KH
@ 2023-10-25 22:14 ` Yu, Richard
0 siblings, 0 replies; 7+ messages in thread
From: Yu, Richard @ 2023-10-25 22:14 UTC (permalink / raw)
To: Greg KH
Cc: Verdun, Jean-Marie, Hawkins, Nick, robh+dt@kernel.org,
krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org,
linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
Hi Greg KH,
Thank you very much for the feedback and sorry for the late respond.
On Thu, Sep 07, 2023 at 04:06:00PM -0500, richard.yu@hpe.com wrote:
>> +struct gxp_udc_drvdata {
>> + void __iomem *base;
>> + struct platform_device *pdev;
>> + struct regmap *udcg_map;
>> + struct gxp_udc_ep ep[GXP_UDC_MAX_NUM_EP];
>> +
>> + int irq;
>> +
>> + /* sysfs enclosure for the gadget gunk */
>> + struct device *port_dev;
> A "raw" struct device? That's not ok. It's also going to break things, how was this tested? What does it look like in sysfs with this device?
I am using aspeed-vhub as example to write this gxp-hub driver. My struct gxp_udc_drvdata{}
Is similar to the struct ast_vhub_dev{} in drivers/usb/gadget/udc/aspeed-vhub/vhub.h
The "struct device *port_dev;" is for the child device which is attached to the hub device.
I tested this driver by modifying the create_usbhid.sh and ikvm_input.hpp in the obmc-ikvm.
The modification is just changing the dev_name to be "80400800.usb-hub". I have made sure
that the KVM is working. (The virtual keyboard and virtual mouse are working).
The devices will be shown as
./sys/devices/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p1
./sys/devices/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p2
./sys/devices/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p3
./sys/devices/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p4
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p1
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p2
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p3
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p4
>> + /*
>> + * The UDC core really needs us to have separate and uniquely
>> + * named "parent" devices for each port so we create a sub device
>> + * here for that purpose
>> + */
>> + drvdata->port_dev = kzalloc(sizeof(*drvdata->port_dev), GFP_KERNEL);
>> + if (!drvdata->port_dev) {
>> + rc = -ENOMEM;
>> + goto fail_alloc;
>> + }
>> + device_initialize(drvdata->port_dev);
>> + drvdata->port_dev->release = gxp_udc_dev_release;
>> + drvdata->port_dev->parent = parent;
>> + dev_set_name(drvdata->port_dev, "%s:p%d", dev_name(parent), idx +
>> +1);
>> +
>> + /* DMA setting */
>> + drvdata->port_dev->dma_mask = parent->dma_mask;
>> + drvdata->port_dev->coherent_dma_mask = parent->coherent_dma_mask;
>> + drvdata->port_dev->bus_dma_limit = parent->bus_dma_limit;
>> + drvdata->port_dev->dma_range_map = parent->dma_range_map;
>> + drvdata->port_dev->dma_parms = parent->dma_parms;
>> + drvdata->port_dev->dma_pools = parent->dma_pools;
>> +
>> + rc = device_add(drvdata->port_dev);
> So you createad a "raw" device that does not belong to any bus or type and add it to sysfs?
> Why? Shouldn't it be a "virtual" device if you really want/need one?
I am just following the aspeed-vhub driver here. I thought I have to build the following entries:
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p1
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p2
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p3
./sys/bus/platform/devices/ahb@80000000/80400800.usb-hub/80400800.usb-hub:p4
In order for the ikvm application to get access the virtual devices.
>> + if (rc)
>> + goto fail_add;
>> +
>> + /* Populate gadget */
>> + gxp_udc_init(drvdata);
>> +
>> + rc = usb_add_gadget_udc(drvdata->port_dev, &drvdata->gadget);
>> + if (rc != 0) {
>> + dev_err(drvdata->port_dev, "add gadget failed\n");
>> + goto fail_udc;
>> + }
>> + rc = devm_request_irq(drvdata->port_dev,
>> + drvdata->irq,
>> + gxp_udc_irq,
>> + IRQF_SHARED,
>> + gxp_udc_name[drvdata->vdevnum],
>> + drvdata);
> devm_request_irq() is _very_ tricky, are you _SURE_ you got it right here? Why do you need to use it?
I thought this is to install my device interrupt handler. Again, I just followed the aspeed-vhub driver. The
Aspeed-vhub driver is doing it at ast_vhub_probe() core.c file.
In previous review, Mr. Kolowski pointed out that this is very tricky using "IRQF_SHARED". I tried all the
Available flag and none are working for me, except "IRQF_SHARED". I also confirmed that the Aspeed-vhub
driver is also using "IRQF_SHARED".
>> + if (rc < 0) {
>> + dev_err(drvdata->port_dev, "irq request failed\n");
>> + goto fail_udc;
>> + }
>> +
>> + return 0;
>> +
>> + /* ran code to simulate these three error exit, no double free */
> What does this comment mean?
I will remove this comment. I put it in there because it was pointed out there is potential double free in
the previous review. I ran through the error exit test cases and did not see any problem.
>> +fail_udc:
>> + device_del(drvdata->port_dev);
>> +fail_add:
>> + put_device(drvdata->port_dev);
>> +fail_alloc:
>> + devm_kfree(parent, drvdata);
>> +
>> + return rc;
>> +}
> Where is the device removed from the system when done?
I will add the device removed routine in the next check-in.
> thanks,
> greg k-h
Thank you very much
Richard.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2023-10-25 22:16 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-09-07 21:05 [PATCH v2 0/3] Add USB driver for HPE GXP Architecture richard.yu
2023-09-07 21:05 ` [PATCH v2 1/3] dt-bindings: usb: Add HPE GXP HUB Controller richard.yu
2023-09-11 14:57 ` Rob Herring
2023-09-07 21:06 ` [PATCH v2 2/3] usb: gadget: udc: gxp-udc: add HPE GXP USB HUB support richard.yu
2023-10-02 12:21 ` Greg KH
2023-10-25 22:14 ` Yu, Richard
2023-09-07 21:06 ` [PATCH v2 3/3] MAINTAINERS: add USB HUB support for GXP richard.yu
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.