diff mbox series

[RESEND] usb: max3420: add the gadget driver

Message ID 20200607205859.55341-1-jassisinghbrar@gmail.com
State Superseded
Headers show
Series [RESEND] usb: max3420: add the gadget driver | expand

Commit Message

Jassi Brar June 7, 2020, 8:58 p.m. UTC
From: Jassi Brar <jaswinder.singh at linaro.org>

MAX3420 implements FullSpeed USB Device over SPI.
Another version MAX3421, also implements USB Host mode.
This driver should be good for the device mode of max3421 as well.

Signed-off-by: Jassi Brar <jaswinder.singh at linaro.org>
---
 drivers/usb/gadget/Kconfig        |   6 +
 drivers/usb/gadget/Makefile       |   1 +
 drivers/usb/gadget/gadget_chips.h |   8 +
 drivers/usb/gadget/max3420_udc.c  | 875 ++++++++++++++++++++++++++++++
 4 files changed, 890 insertions(+)
 create mode 100644 drivers/usb/gadget/max3420_udc.c

Comments

Jassi Brar June 15, 2020, 11:25 p.m. UTC | #1
... a polite ping, Lukasz.

On Sun, Jun 7, 2020 at 3:59 PM <jassisinghbrar at gmail.com> wrote:
>
> From: Jassi Brar <jaswinder.singh at linaro.org>
>
> MAX3420 implements FullSpeed USB Device over SPI.
> Another version MAX3421, also implements USB Host mode.
> This driver should be good for the device mode of max3421 as well.
>
> Signed-off-by: Jassi Brar <jaswinder.singh at linaro.org>
> ---
>  drivers/usb/gadget/Kconfig        |   6 +
>  drivers/usb/gadget/Makefile       |   1 +
>  drivers/usb/gadget/gadget_chips.h |   8 +
>  drivers/usb/gadget/max3420_udc.c  | 875 ++++++++++++++++++++++++++++++
>  4 files changed, 890 insertions(+)
>  create mode 100644 drivers/usb/gadget/max3420_udc.c
>
> diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
> index 46aa3fe954..7c0df5c264 100644
> --- a/drivers/usb/gadget/Kconfig
> +++ b/drivers/usb/gadget/Kconfig
> @@ -105,6 +105,12 @@ config CI_UDC
>           Say Y here to enable device controller functionality of the
>           ChipIdea driver.
>
> +config USB_GADGET_MAX3420
> +       bool "MAX3420 USB Over SPI"
> +       depends on DM_SPI
> +       help
> +         MAX3420, from MAXIM, implements USB-over-SPI Full-Speed device controller.
> +
>  config USB_GADGET_VBUS_DRAW
>         int "Maximum VBUS Power usage (2-500 mA)"
>         range 2 500
> diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
> index 70f3bf43e7..f560068b41 100644
> --- a/drivers/usb/gadget/Makefile
> +++ b/drivers/usb/gadget/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_USB_GADGET_BCM_UDC_OTG_PHY) += bcm_udc_otg_phy.o
>  obj-$(CONFIG_USB_GADGET_DWC2_OTG) += dwc2_udc_otg.o
>  obj-$(CONFIG_USB_GADGET_DWC2_OTG_PHY) += dwc2_udc_otg_phy.o
>  obj-$(CONFIG_USB_GADGET_FOTG210) += fotg210.o
> +obj-$(CONFIG_USB_GADGET_MAX3420) += max3420_udc.o
>  obj-$(CONFIG_CI_UDC)   += ci_udc.o
>  ifndef CONFIG_SPL_BUILD
>  obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o
> diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
> index 91b0285244..587204cfb7 100644
> --- a/drivers/usb/gadget/gadget_chips.h
> +++ b/drivers/usb/gadget/gadget_chips.h
> @@ -155,6 +155,12 @@
>  #define gadget_is_cdns3(g)        0
>  #endif
>
> +#ifdef CONFIG_USB_GADGET_MAX3420
> +#define gadget_is_max3420(g)        (!strcmp("max3420-udc", (g)->name))
> +#else
> +#define gadget_is_max3420(g)        0
> +#endif
> +
>  /**
>   * usb_gadget_controller_number - support bcdDevice id convention
>   * @gadget: the controller being driven
> @@ -216,5 +222,7 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
>                 return 0x23;
>         else if (gadget_is_cdns3(gadget))
>                 return 0x24;
> +       else if (gadget_is_max3420(gadget))
> +               return 0x25;
>         return -ENOENT;
>  }
> diff --git a/drivers/usb/gadget/max3420_udc.c b/drivers/usb/gadget/max3420_udc.c
> new file mode 100644
> index 0000000000..7267d482b8
> --- /dev/null
> +++ b/drivers/usb/gadget/max3420_udc.c
> @@ -0,0 +1,875 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +
> +#include <common.h>
> +#include <linux/errno.h>
> +#include <asm/gpio.h>
> +#include <linux/list.h>
> +#include <linux/usb/ch9.h>
> +#include <linux/usb/gadget.h>
> +#include <malloc.h>
> +#include <spi.h>
> +#include <dm.h>
> +#include <g_dnl.h>
> +
> +#define MAX3420_MAX_EPS                4
> +#define EP_MAX_PACKET          64  /* Same for all Endpoints */
> +#define EPNAME_SIZE            16  /* Buffer size for endpoint name */
> +
> +#define ACKSTAT                BIT(0)
> +
> +#define MAX3420_SPI_DIR_RD     0       /* read register from MAX3420 */
> +#define MAX3420_SPI_DIR_WR     1       /* write register to MAX3420 */
> +
> +/* SPI commands: */
> +#define MAX3420_SPI_DIR_SHIFT  1
> +#define MAX3420_SPI_REG_SHIFT  3
> +
> +#define MAX3420_REG_EP0FIFO    0
> +#define MAX3420_REG_EP1FIFO    1
> +#define MAX3420_REG_EP2FIFO    2
> +#define MAX3420_REG_EP3FIFO    3
> +#define MAX3420_REG_SUDFIFO    4
> +#define MAX3420_REG_EP0BC      5
> +#define MAX3420_REG_EP1BC      6
> +#define MAX3420_REG_EP2BC      7
> +#define MAX3420_REG_EP3BC      8
> +
> +#define MAX3420_REG_EPSTALLS   9
> +       #define bACKSTAT        BIT(6)
> +       #define bSTLSTAT        BIT(5)
> +       #define bSTLEP3IN       BIT(4)
> +       #define bSTLEP2IN       BIT(3)
> +       #define bSTLEP1OUT      BIT(2)
> +       #define bSTLEP0OUT      BIT(1)
> +       #define bSTLEP0IN       BIT(0)
> +
> +#define MAX3420_REG_CLRTOGS    10
> +       #define bEP3DISAB       BIT(7)
> +       #define bEP2DISAB       BIT(6)
> +       #define bEP1DISAB       BIT(5)
> +       #define bCTGEP3IN       BIT(4)
> +       #define bCTGEP2IN       BIT(3)
> +       #define bCTGEP1OUT      BIT(2)
> +
> +#define MAX3420_REG_EPIRQ      11
> +#define MAX3420_REG_EPIEN      12
> +       #define bSUDAVIRQ       BIT(5)
> +       #define bIN3BAVIRQ      BIT(4)
> +       #define bIN2BAVIRQ      BIT(3)
> +       #define bOUT1DAVIRQ     BIT(2)
> +       #define bOUT0DAVIRQ     BIT(1)
> +       #define bIN0BAVIRQ      BIT(0)
> +
> +#define MAX3420_REG_USBIRQ     13
> +#define MAX3420_REG_USBIEN     14
> +       #define bOSCOKIRQ       BIT(0)
> +       #define bRWUDNIRQ       BIT(1)
> +       #define bBUSACTIRQ      BIT(2)
> +       #define bURESIRQ        BIT(3)
> +       #define bSUSPIRQ        BIT(4)
> +       #define bNOVBUSIRQ      BIT(5)
> +       #define bVBUSIRQ        BIT(6)
> +       #define bURESDNIRQ      BIT(7)
> +
> +#define MAX3420_REG_USBCTL     15
> +       #define bHOSCSTEN       BIT(7)
> +       #define bVBGATE         BIT(6)
> +       #define bCHIPRES        BIT(5)
> +       #define bPWRDOWN        BIT(4)
> +       #define bCONNECT        BIT(3)
> +       #define bSIGRWU         BIT(2)
> +
> +#define MAX3420_REG_CPUCTL     16
> +       #define bIE             BIT(0)
> +
> +#define MAX3420_REG_PINCTL     17
> +       #define bEP3INAK        BIT(7)
> +       #define bEP2INAK        BIT(6)
> +       #define bEP0INAK        BIT(5)
> +       #define bFDUPSPI        BIT(4)
> +       #define bINTLEVEL       BIT(3)
> +       #define bPOSINT         BIT(2)
> +       #define bGPXB           BIT(1)
> +       #define bGPXA           BIT(0)
> +
> +#define MAX3420_REG_REVISION   18
> +
> +#define MAX3420_REG_FNADDR     19
> +       #define FNADDR_MASK     0x7f
> +
> +#define MAX3420_REG_IOPINS     20
> +#define MAX3420_REG_IOPINS2    21
> +#define MAX3420_REG_GPINIRQ    22
> +#define MAX3420_REG_GPINIEN    23
> +#define MAX3420_REG_GPINPOL    24
> +#define MAX3420_REG_HIRQ       25
> +#define MAX3420_REG_HIEN       26
> +#define MAX3420_REG_MODE       27
> +#define MAX3420_REG_PERADDR    28
> +#define MAX3420_REG_HCTL       29
> +#define MAX3420_REG_HXFR       30
> +#define MAX3420_REG_HRSL       31
> +
> +#define field(val, bit)        ((val) << (bit))
> +
> +#define msleep(a)      udelay((a) * 1000)
> +
> +struct max3420_req {
> +       struct usb_request usb_req;
> +       struct list_head queue;
> +       struct max3420_ep *ep;
> +};
> +
> +struct max3420_ep {
> +       struct max3420_udc *udc;
> +       struct list_head queue;
> +       char name[EPNAME_SIZE];
> +       unsigned int maxpacket;
> +       struct usb_ep ep_usb;
> +       int halted;
> +       int id;
> +};
> +
> +struct max3420_udc {
> +       struct max3420_ep ep[MAX3420_MAX_EPS];
> +       struct usb_gadget_driver *driver;
> +       bool vbus_active, softconnect;
> +       struct usb_ctrlrequest setup;
> +       struct max3420_req ep0req;
> +       struct usb_gadget gadget;
> +       struct spi_slave *slave;
> +       struct udevice *dev;
> +       u8 ep0buf[64];
> +       int remote_wkp;
> +       bool suspended;
> +};
> +
> +#define to_max3420_req(r)      container_of((r), struct max3420_req, usb_req)
> +#define to_max3420_ep(e)       container_of((e), struct max3420_ep, ep_usb)
> +#define to_udc(g)              container_of((g), struct max3420_udc, gadget)
> +
> +static void spi_ack_ctrl(struct max3420_udc *udc)
> +{
> +       struct spi_slave *slave = udc->slave;
> +       u8 txdata[1];
> +
> +       txdata[0] = ACKSTAT;
> +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_ONCE);
> +}
> +
> +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int actstat)
> +{
> +       struct spi_slave *slave = udc->slave;
> +       u8 txdata[2], rxdata[2];
> +
> +       txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
> +                       field(MAX3420_SPI_DIR_RD, MAX3420_SPI_DIR_SHIFT) |
> +                       (actstat ? ACKSTAT : 0);
> +
> +       rxdata[0] = 0;
> +       rxdata[1] = 0;
> +       spi_xfer(slave, 2 * 8, txdata, rxdata, SPI_XFER_ONCE);
> +
> +       return rxdata[1];
> +}
> +
> +static u8 spi_rd8(struct max3420_udc *udc, u8 reg)
> +{
> +       return spi_rd8_ack(udc, reg, 0);
> +}
> +
> +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val, int actstat)
> +{
> +       struct spi_slave *slave = udc->slave;
> +       u8 txdata[2];
> +
> +       txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
> +                       field(MAX3420_SPI_DIR_WR, MAX3420_SPI_DIR_SHIFT) |
> +                       (actstat ? ACKSTAT : 0);
> +       txdata[1] = val;
> +
> +       spi_xfer(slave, 2 * 8, txdata, NULL, SPI_XFER_ONCE);
> +}
> +
> +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val)
> +{
> +       spi_wr8_ack(udc, reg, val, 0);
> +}
> +
> +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len)
> +{
> +       struct spi_slave *slave = udc->slave;
> +       u8 txdata[1];
> +
> +       txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
> +                        field(MAX3420_SPI_DIR_RD, MAX3420_SPI_DIR_SHIFT));
> +
> +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);
> +       spi_xfer(slave, len * 8, NULL, buf, SPI_XFER_END);
> +}
> +
> +static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len)
> +{
> +       struct spi_slave *slave = udc->slave;
> +       u8 txdata[1];
> +
> +       txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
> +                        field(MAX3420_SPI_DIR_WR, MAX3420_SPI_DIR_SHIFT));
> +
> +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);
> +       spi_xfer(slave, len * 8, buf, NULL, SPI_XFER_END);
> +}
> +
> +/* 0 if not-connected */
> +int g_dnl_board_usb_cable_connected(void)
> +{
> +       return 1;
> +}
> +
> +static void spi_max3420_enable(struct max3420_ep *ep, int enable)
> +{
> +       struct max3420_udc *udc = ep->udc;
> +       u8 epdis, epien;
> +
> +       if (ep->id == 0)
> +               return;
> +
> +       epien = spi_rd8(udc, MAX3420_REG_EPIEN);
> +       epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS);
> +
> +       if (enable) {
> +               epdis &= ~BIT(ep->id + 4);
> +               epien |= BIT(ep->id + 1);
> +       } else {
> +               epdis |= BIT(ep->id + 4);
> +               epien &= ~BIT(ep->id + 1);
> +       }
> +
> +       spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis);
> +       spi_wr8(udc, MAX3420_REG_EPIEN, epien);
> +}
> +
> +static int
> +max3420_ep_enable(struct usb_ep *_ep,
> +                 const struct usb_endpoint_descriptor *desc)
> +{
> +       struct max3420_ep *ep = to_max3420_ep(_ep);
> +
> +       _ep->desc = desc;
> +       _ep->maxpacket = usb_endpoint_maxp(desc) & 0x7ff;
> +
> +       spi_max3420_enable(ep, 1);
> +
> +       return 0;
> +}
> +
> +static void max3420_req_done(struct max3420_req *req, int status)
> +{
> +       struct max3420_ep *ep = req->ep;
> +
> +       if (req->usb_req.status == -EINPROGRESS)
> +               req->usb_req.status = status;
> +       else
> +               status = req->usb_req.status;
> +
> +       if (status && status != -ESHUTDOWN)
> +               dev_err(ep->udc->dev, "%s done %p, status %d\n",
> +                       ep->ep_usb.name, req, status);
> +
> +       if (req->usb_req.complete)
> +               req->usb_req.complete(&ep->ep_usb, &req->usb_req);
> +}
> +
> +static void max3420_ep_nuke(struct max3420_ep *ep, int status)
> +{
> +       struct max3420_req *req, *r;
> +
> +       list_for_each_entry_safe(req, r, &ep->queue, queue) {
> +               list_del_init(&req->queue);
> +               max3420_req_done(req, status);
> +       }
> +}
> +
> +static int max3420_ep_disable(struct usb_ep *_ep)
> +{
> +       struct max3420_ep *ep = to_max3420_ep(_ep);
> +
> +       _ep->desc = NULL;
> +       max3420_ep_nuke(ep, -ESHUTDOWN);
> +       spi_max3420_enable(ep, 0);
> +
> +       return 0;
> +}
> +
> +static struct usb_request *
> +max3420_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
> +{
> +       struct max3420_ep *ep = to_max3420_ep(_ep);
> +       struct max3420_req *req = kzalloc(sizeof(*req), gfp_flags);
> +
> +       if (!req)
> +               return NULL;
> +
> +       req->ep = ep;
> +       INIT_LIST_HEAD(&req->queue);
> +
> +       return &req->usb_req;
> +}
> +
> +static void
> +max3420_ep_free_request(struct usb_ep *_ep, struct usb_request *_req)
> +{
> +       kfree(to_max3420_req(_req));
> +}
> +
> +static int
> +max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
> +{
> +       struct max3420_req *req = to_max3420_req(_req);
> +       struct max3420_ep *ep = to_max3420_ep(_ep);
> +
> +       _req->status = -EINPROGRESS;
> +       _req->actual = 0;
> +       list_add_tail(&req->queue, &ep->queue);
> +
> +       return 0;
> +}
> +
> +static int max3420_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
> +{
> +       struct max3420_req *req = to_max3420_req(_req);
> +
> +       list_del_init(&req->queue);
> +       max3420_req_done(req, -ECONNRESET);
> +
> +       return 0;
> +}
> +
> +static int max3420_ep_set_halt(struct usb_ep *_ep, int halt)
> +{
> +       struct max3420_ep *ep = to_max3420_ep(_ep);
> +       struct max3420_udc *udc = ep->udc;
> +       u8 epstalls;
> +
> +       if (ep->id == 0) /* can't stall EP0 */
> +               return 0;
> +
> +       epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS);
> +       if (halt) {
> +               ep->halted = 1;
> +               epstalls |= BIT(ep->id + 1);
> +       } else {
> +               u8 clrtogs;
> +
> +               ep->halted = 0;
> +               epstalls &= ~BIT(ep->id + 1);
> +               clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS);
> +               clrtogs |= BIT(ep->id + 1);
> +               spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs);
> +       }
> +       spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | bACKSTAT);
> +
> +       return 0;
> +}
> +
> +static const struct usb_ep_ops max3420_ep_ops = {
> +       .enable         = max3420_ep_enable,
> +       .disable        = max3420_ep_disable,
> +       .alloc_request  = max3420_ep_alloc_request,
> +       .free_request   = max3420_ep_free_request,
> +       .queue          = max3420_ep_queue,
> +       .dequeue        = max3420_ep_dequeue,
> +       .set_halt       = max3420_ep_set_halt,
> +};
> +
> +static void __max3420_stop(struct max3420_udc *udc)
> +{
> +       u8 val;
> +
> +       /* Disable IRQ to CPU */
> +       spi_wr8(udc, MAX3420_REG_CPUCTL, 0);
> +
> +       val = spi_rd8(udc, MAX3420_REG_USBCTL);
> +       val |= bPWRDOWN;
> +       val |= bHOSCSTEN;
> +       spi_wr8(udc, MAX3420_REG_USBCTL, val);
> +}
> +
> +static void __max3420_start(struct max3420_udc *udc)
> +{
> +       u8 val;
> +
> +       /* configure SPI */
> +       spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI);
> +
> +       /* Chip Reset */
> +       spi_wr8(udc, MAX3420_REG_USBCTL, bCHIPRES);
> +       msleep(5);
> +       spi_wr8(udc, MAX3420_REG_USBCTL, 0);
> +
> +       /* Poll for OSC to stabilize */
> +       while (1) {
> +               val = spi_rd8(udc, MAX3420_REG_USBIRQ);
> +               if (val & bOSCOKIRQ)
> +                       break;
> +               cond_resched();
> +       }
> +
> +       /* Enable PULL-UP only when Vbus detected */
> +       val = spi_rd8(udc, MAX3420_REG_USBCTL);
> +       val |= bVBGATE | bCONNECT;
> +       spi_wr8(udc, MAX3420_REG_USBCTL, val);
> +
> +       val = bURESDNIRQ | bURESIRQ;
> +       spi_wr8(udc, MAX3420_REG_USBIEN, val);
> +
> +       /* Enable only EP0 interrupts */
> +       val = bIN0BAVIRQ | bOUT0DAVIRQ | bSUDAVIRQ;
> +       spi_wr8(udc, MAX3420_REG_EPIEN, val);
> +
> +       /* Enable IRQ to CPU */
> +       spi_wr8(udc, MAX3420_REG_CPUCTL, bIE);
> +}
> +
> +static int max3420_udc_start(struct usb_gadget *gadget,
> +                            struct usb_gadget_driver *driver)
> +{
> +       struct max3420_udc *udc = to_udc(gadget);
> +       unsigned long flags;
> +
> +       udc->driver = driver;
> +       udc->remote_wkp = 0;
> +       udc->softconnect = true;
> +
> +       //if (udc->vbus_active)
> +               __max3420_start(udc);
> +
> +       return 0;
> +}
> +
> +static int max3420_udc_stop(struct usb_gadget *gadget)
> +{
> +       struct max3420_udc *udc = to_udc(gadget);
> +
> +       udc->driver = NULL;
> +       udc->softconnect = false;
> +
> +       __max3420_stop(udc);
> +
> +       return 0;
> +}
> +
> +static int max3420_wakeup(struct usb_gadget *gadget)
> +{
> +       struct max3420_udc *udc = to_udc(gadget);
> +       u8 usbctl;
> +
> +       /* Only if wakeup allowed by host */
> +       if (!udc->remote_wkp || !udc->suspended)
> +               return 0;
> +
> +       /* Set Remote-WkUp Signal*/
> +       usbctl = spi_rd8(udc, MAX3420_REG_USBCTL);
> +       usbctl |= bSIGRWU;
> +       spi_wr8(udc, MAX3420_REG_USBCTL, usbctl);
> +
> +       msleep(5);
> +
> +       /* Clear Remote-WkUp Signal*/
> +       usbctl = spi_rd8(udc, MAX3420_REG_USBCTL);
> +       usbctl &= ~bSIGRWU;
> +       spi_wr8(udc, MAX3420_REG_USBCTL, usbctl);
> +
> +       udc->suspended = false;
> +
> +       return 0;
> +}
> +
> +static const struct usb_gadget_ops max3420_udc_ops = {
> +       .udc_start      = max3420_udc_start,
> +       .udc_stop       = max3420_udc_stop,
> +       .wakeup         = max3420_wakeup,
> +};
> +
> +static struct usb_endpoint_descriptor ep0_desc = {
> +       .bLength = USB_DT_ENDPOINT_SIZE,
> +       .bDescriptorType = USB_DT_ENDPOINT,
> +       .bEndpointAddress = USB_DIR_OUT,
> +       .bmAttributes = USB_ENDPOINT_XFER_CONTROL,
> +       .wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET),
> +};
> +
> +static void max3420_getstatus(struct max3420_udc *udc)
> +{
> +       struct max3420_ep *ep;
> +       u16 status = 0;
> +
> +       switch (udc->setup.bRequestType & USB_RECIP_MASK) {
> +       case USB_RECIP_DEVICE:
> +               /* Get device status */
> +               status = 0 << USB_DEVICE_SELF_POWERED;
> +               status |= (udc->remote_wkp << USB_DEVICE_REMOTE_WAKEUP);
> +               break;
> +       case USB_RECIP_INTERFACE:
> +               if (udc->driver->setup(&udc->gadget, &udc->setup) < 0)
> +                       goto stall;
> +               break;
> +       case USB_RECIP_ENDPOINT:
> +               ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK];
> +               if (ep->halted)
> +                       status = 1 << USB_ENDPOINT_HALT;
> +               break;
> +       default:
> +               goto stall;
> +       }
> +
> +       status = cpu_to_le16(status);
> +       spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2);
> +       spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1);
> +       return;
> +stall:
> +       dev_err(udc->dev, "Can't respond to getstatus request\n");
> +       spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
> +}
> +
> +static void max3420_set_clear_feature(struct max3420_udc *udc)
> +{
> +       int set = udc->setup.bRequest == USB_REQ_SET_FEATURE;
> +       struct max3420_ep *ep;
> +       int id;
> +
> +       switch (udc->setup.bRequestType) {
> +       case USB_RECIP_DEVICE:
> +               if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP)
> +                       break;
> +
> +               if (udc->setup.bRequest == USB_REQ_SET_FEATURE)
> +                       udc->remote_wkp = 1;
> +               else
> +                       udc->remote_wkp = 0;
> +
> +               return spi_ack_ctrl(udc);
> +
> +       case USB_RECIP_ENDPOINT:
> +               if (udc->setup.wValue != USB_ENDPOINT_HALT)
> +                       break;
> +
> +               id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;
> +               ep = &udc->ep[id];
> +
> +               max3420_ep_set_halt(&ep->ep_usb, set);
> +               return;
> +       default:
> +               break;
> +       }
> +
> +       dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n");
> +       spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
> +}
> +
> +static void max3420_handle_setup(struct max3420_udc *udc)
> +{
> +       struct usb_ctrlrequest setup;
> +       u8 addr;
> +
> +       spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8);
> +
> +       udc->setup = setup;
> +       udc->setup.wValue = cpu_to_le16(setup.wValue);
> +       udc->setup.wIndex = cpu_to_le16(setup.wIndex);
> +       udc->setup.wLength = cpu_to_le16(setup.wLength);
> +
> +       switch (udc->setup.bRequest) {
> +       case USB_REQ_GET_STATUS:
> +               /* Data+Status phase form udc */
> +               if ((udc->setup.bRequestType &
> +                               (USB_DIR_IN | USB_TYPE_MASK)) !=
> +                               (USB_DIR_IN | USB_TYPE_STANDARD)) {
> +                       break;
> +               }
> +               return max3420_getstatus(udc);
> +       case USB_REQ_SET_ADDRESS:
> +               /* Status phase from udc */
> +               if (udc->setup.bRequestType != (USB_DIR_OUT |
> +                               USB_TYPE_STANDARD | USB_RECIP_DEVICE))
> +                       break;
> +               addr = spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1);
> +               dev_dbg(udc->dev, "Assigned Address=%d/%d\n",
> +                       udc->setup.wValue, addr);
> +               return;
> +       case USB_REQ_CLEAR_FEATURE:
> +       case USB_REQ_SET_FEATURE:
> +               /* Requests with no data phase, status phase from udc */
> +               if ((udc->setup.bRequestType & USB_TYPE_MASK)
> +                               != USB_TYPE_STANDARD)
> +                       break;
> +               return max3420_set_clear_feature(udc);
> +       default:
> +               break;
> +       }
> +
> +       if (udc->driver->setup(&udc->gadget, &setup) < 0) {
> +               /* Stall EP0 */
> +               spi_wr8(udc, MAX3420_REG_EPSTALLS,
> +                       bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
> +       }
> +}
> +
> +static int do_data(struct max3420_udc *udc, int ep_id, int in)
> +{
> +       struct max3420_ep *ep = &udc->ep[ep_id];
> +       struct max3420_req *req;
> +       int done, length, psz;
> +       void *buf;
> +
> +       if (list_empty(&ep->queue))
> +               return 0;
> +
> +       req = list_first_entry(&ep->queue, struct max3420_req, queue);
> +       buf = req->usb_req.buf + req->usb_req.actual;
> +
> +       psz = ep->ep_usb.maxpacket;
> +       length = req->usb_req.length - req->usb_req.actual;
> +       length = min(length, psz);
> +
> +       if (length == 0) {
> +               done = 1;
> +               goto xfer_done;
> +       }
> +
> +       done = 0;
> +       if (in) {
> +               spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length);
> +               spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length);
> +               if (length < psz)
> +                       done = 1;
> +       } else {
> +               psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id);
> +               length = min(length, psz);
> +               spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length);
> +               if (length < ep->ep_usb.maxpacket)
> +                       done = 1;
> +       }
> +
> +       req->usb_req.actual += length;
> +
> +       if (req->usb_req.actual == req->usb_req.length)
> +               done = 1;
> +
> +xfer_done:
> +       if (done) {
> +               unsigned long flags;
> +
> +               list_del_init(&req->queue);
> +
> +               if (ep_id == 0)
> +                       spi_ack_ctrl(udc);
> +
> +               max3420_req_done(req, 0);
> +       }
> +
> +       return 1;
> +}
> +
> +static int max3420_handle_irqs(struct max3420_udc *udc)
> +{
> +       u8 epien, epirq, usbirq, usbien, reg[4];
> +       int ret = 0;
> +
> +       spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4);
> +       epirq = reg[0];
> +       epien = reg[1];
> +       usbirq = reg[2];
> +       usbien = reg[3];
> +
> +       usbirq &= usbien;
> +       epirq &= epien;
> +
> +       if (epirq & bSUDAVIRQ) {
> +               spi_wr8(udc, MAX3420_REG_EPIRQ, bSUDAVIRQ);
> +               max3420_handle_setup(udc);
> +               return 1;
> +       }
> +
> +       if (usbirq & bVBUSIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bVBUSIRQ);
> +               dev_dbg(udc->dev, "Cable plugged in\n");
> +               g_dnl_clear_detach();
> +               return 1;
> +       }
> +
> +       if (usbirq & bNOVBUSIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bNOVBUSIRQ);
> +               dev_dbg(udc->dev, "Cable pulled out\n");
> +               g_dnl_trigger_detach();
> +               return 1;
> +       }
> +
> +       if (usbirq & bURESIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bURESIRQ);
> +               return 1;
> +       }
> +
> +       if (usbirq & bURESDNIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bURESDNIRQ);
> +               spi_wr8(udc, MAX3420_REG_USBIEN, bURESDNIRQ | bURESIRQ);
> +               spi_wr8(udc, MAX3420_REG_EPIEN, bSUDAVIRQ
> +                       | bIN0BAVIRQ | bOUT0DAVIRQ);
> +               return 1;
> +       }
> +
> +       if (usbirq & bSUSPIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bSUSPIRQ);
> +               dev_dbg(udc->dev, "USB Suspend - Enter\n");
> +               udc->suspended = true;
> +               return 1;
> +       }
> +
> +       if (usbirq & bBUSACTIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bBUSACTIRQ);
> +               dev_dbg(udc->dev, "USB Suspend - Exit\n");
> +               udc->suspended = false;
> +               return 1;
> +       }
> +
> +       if (usbirq & bRWUDNIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bRWUDNIRQ);
> +               dev_dbg(udc->dev, "Asked Host to wakeup\n");
> +               return 1;
> +       }
> +
> +       if (usbirq & bOSCOKIRQ) {
> +               spi_wr8(udc, MAX3420_REG_USBIRQ, bOSCOKIRQ);
> +               dev_dbg(udc->dev, "Osc stabilized, start work\n");
> +               return 1;
> +       }
> +
> +       if (epirq & bOUT0DAVIRQ && do_data(udc, 0, 0)) {
> +               spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT0DAVIRQ, 1);
> +               ret = 1;
> +       }
> +
> +       if (epirq & bIN0BAVIRQ && do_data(udc, 0, 1))
> +               ret = 1;
> +
> +       if (epirq & bOUT1DAVIRQ && do_data(udc, 1, 0)) {
> +               spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT1DAVIRQ, 1);
> +               ret = 1;
> +       }
> +
> +       if (epirq & bIN2BAVIRQ && do_data(udc, 2, 1))
> +               ret = 1;
> +
> +       if (epirq & bIN3BAVIRQ && do_data(udc, 3, 1))
> +               ret = 1;
> +
> +       return ret;
> +}
> +
> +static int max3420_irq(struct max3420_udc *udc)
> +{
> +       /* needed ? */
> +       do_data(udc, 0, 1); /* get done with the EP0 ZLP */
> +
> +       return max3420_handle_irqs(udc);
> +}
> +
> +static void max3420_setup_eps(struct max3420_udc *udc)
> +{
> +       int i;
> +
> +       INIT_LIST_HEAD(&udc->gadget.ep_list);
> +       INIT_LIST_HEAD(&udc->ep[0].ep_usb.ep_list);
> +
> +       for (i = 0; i < MAX3420_MAX_EPS; i++) {
> +               struct max3420_ep *ep = &udc->ep[i];
> +
> +               INIT_LIST_HEAD(&ep->queue);
> +
> +               ep->id = i;
> +               ep->udc = udc;
> +               ep->ep_usb.ops = &max3420_ep_ops;
> +               ep->ep_usb.name = ep->name;
> +               ep->ep_usb.maxpacket = EP_MAX_PACKET;
> +
> +               if (i == 0) {
> +                       ep->ep_usb.desc = &ep0_desc;
> +                       snprintf(ep->name, EPNAME_SIZE, "ep0");
> +                       continue;
> +               }
> +
> +               list_add_tail(&ep->ep_usb.ep_list, &udc->gadget.ep_list);
> +
> +               if (i == 1)
> +                       snprintf(ep->name, EPNAME_SIZE, "ep1out-bulk");
> +               else
> +                       snprintf(ep->name, EPNAME_SIZE, "ep%din-bulk", i);
> +       };
> +}
> +
> +static void max3420_setup_spi(struct max3420_udc *udc)
> +{
> +       u8 reg[8];
> +
> +       spi_claim_bus(udc->slave);
> +       /* necessary? */
> +       spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8);
> +       /* configure SPI */
> +       spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI);
> +}
> +
> +int dm_usb_gadget_handle_interrupts(struct udevice *dev)
> +{
> +       struct max3420_udc *udc = dev_get_priv(dev);
> +
> +       return max3420_irq(udc);
> +}
> +
> +static int max3420_udc_probe(struct udevice *dev)
> +{
> +       struct max3420_udc *udc = dev_get_priv(dev);
> +       struct udevice *spid;
> +
> +       spi_get_bus_and_cs(3, 0, 10000000, SPI_MODE_3, "spi_generic_drv",
> +                          NULL, &spid, &udc->slave);
> +
> +       udc->dev = dev;
> +       udc->gadget.ep0 = &udc->ep[0].ep_usb;
> +       udc->gadget.max_speed = USB_SPEED_FULL;
> +       udc->gadget.speed = USB_SPEED_FULL;
> +       udc->gadget.is_dualspeed = 0;
> +       udc->gadget.ops = &max3420_udc_ops;
> +       udc->gadget.name = "max3420-udc";
> +
> +       max3420_setup_eps(udc);
> +       max3420_setup_spi(udc);
> +
> +       usb_add_gadget_udc((struct device *)dev, &udc->gadget);
> +
> +       return 0;
> +}
> +
> +static int max3420_udc_remove(struct udevice *dev)
> +{
> +       struct max3420_udc *udc = dev_get_priv(dev);
> +
> +       usb_del_gadget_udc(&udc->gadget);
> +
> +       spi_release_bus(udc->slave);
> +
> +       return 0;
> +}
> +
> +static const struct udevice_id max3420_ids[] = {
> +       { .compatible = "maxim,max3421-udc" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(max3420_generic_udc) = {
> +       .name = "max3420-udc",
> +       .id = UCLASS_USB_GADGET_GENERIC,
> +       .of_match = max3420_ids,
> +       .probe = max3420_udc_probe,
> +       .remove = max3420_udc_remove,
> +       .priv_auto_alloc_size = sizeof(struct max3420_udc),
> +};
> --
> 2.25.1
>
Marek Vasut June 15, 2020, 11:49 p.m. UTC | #2
On 6/16/20 1:25 AM, Jassi Brar wrote:
> ... a polite ping, Lukasz.

so while I don't know much about the gadget subsystem, a few nits below.

[...]

>> +#define MAX3420_REG_MODE       27
>> +#define MAX3420_REG_PERADDR    28
>> +#define MAX3420_REG_HCTL       29
>> +#define MAX3420_REG_HXFR       30
>> +#define MAX3420_REG_HRSL       31
>> +
>> +#define field(val, bit)        ((val) << (bit))

Is this GENMASK() ?

>> +#define msleep(a)      udelay((a) * 1000)

Please use mdelay() .

[...]

>> +static int max3420_udc_start(struct usb_gadget *gadget,
>> +                            struct usb_gadget_driver *driver)
>> +{
>> +       struct max3420_udc *udc = to_udc(gadget);
>> +       unsigned long flags;
>> +
>> +       udc->driver = driver;
>> +       udc->remote_wkp = 0;
>> +       udc->softconnect = true;
>> +
>> +       //if (udc->vbus_active)

Is this intended to be commented out ?

>> +               __max3420_start(udc);
>> +
>> +       return 0;
>> +}

The rest looks good, thanks!
And sorry for the inactivity.
Lukasz Majewski June 16, 2020, 7:29 a.m. UTC | #3
Hi Jassi,

> ... a polite ping, Lukasz.

The only excuse for so long lack of my response are my personal issues
caused by the covid-19.

Sorry for that..

> 
> On Sun, Jun 7, 2020 at 3:59 PM <jassisinghbrar at gmail.com> wrote:
> >
> > From: Jassi Brar <jaswinder.singh at linaro.org>
> >
> > MAX3420 implements FullSpeed USB Device over SPI.
> > Another version MAX3421, also implements USB Host mode.
> > This driver should be good for the device mode of max3421 as well.
> >
> > Signed-off-by: Jassi Brar <jaswinder.singh at linaro.org>
> > ---
> >  drivers/usb/gadget/Kconfig        |   6 +
> >  drivers/usb/gadget/Makefile       |   1 +
> >  drivers/usb/gadget/gadget_chips.h |   8 +
> >  drivers/usb/gadget/max3420_udc.c  | 875
> > ++++++++++++++++++++++++++++++ 4 files changed, 890 insertions(+)
> >  create mode 100644 drivers/usb/gadget/max3420_udc.c
> >
> > diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
> > index 46aa3fe954..7c0df5c264 100644
> > --- a/drivers/usb/gadget/Kconfig
> > +++ b/drivers/usb/gadget/Kconfig
> > @@ -105,6 +105,12 @@ config CI_UDC
> >           Say Y here to enable device controller functionality of
> > the ChipIdea driver.
> >
> > +config USB_GADGET_MAX3420
> > +       bool "MAX3420 USB Over SPI"
> > +       depends on DM_SPI
> > +       help
> > +         MAX3420, from MAXIM, implements USB-over-SPI Full-Speed
> > device controller. +
> >  config USB_GADGET_VBUS_DRAW
> >         int "Maximum VBUS Power usage (2-500 mA)"
> >         range 2 500
> > diff --git a/drivers/usb/gadget/Makefile
> > b/drivers/usb/gadget/Makefile index 70f3bf43e7..f560068b41 100644
> > --- a/drivers/usb/gadget/Makefile
> > +++ b/drivers/usb/gadget/Makefile
> > @@ -20,6 +20,7 @@ obj-$(CONFIG_USB_GADGET_BCM_UDC_OTG_PHY) +=
> > bcm_udc_otg_phy.o obj-$(CONFIG_USB_GADGET_DWC2_OTG) +=
> > dwc2_udc_otg.o obj-$(CONFIG_USB_GADGET_DWC2_OTG_PHY) +=
> > dwc2_udc_otg_phy.o obj-$(CONFIG_USB_GADGET_FOTG210) += fotg210.o
> > +obj-$(CONFIG_USB_GADGET_MAX3420) += max3420_udc.o
> >  obj-$(CONFIG_CI_UDC)   += ci_udc.o
> >  ifndef CONFIG_SPL_BUILD
> >  obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o
> > diff --git a/drivers/usb/gadget/gadget_chips.h
> > b/drivers/usb/gadget/gadget_chips.h index 91b0285244..587204cfb7
> > 100644 --- a/drivers/usb/gadget/gadget_chips.h
> > +++ b/drivers/usb/gadget/gadget_chips.h
> > @@ -155,6 +155,12 @@
> >  #define gadget_is_cdns3(g)        0
> >  #endif
> >
> > +#ifdef CONFIG_USB_GADGET_MAX3420
> > +#define gadget_is_max3420(g)        (!strcmp("max3420-udc",
> > (g)->name)) +#else
> > +#define gadget_is_max3420(g)        0
> > +#endif
> > +

Ok.

> >  /**
> >   * usb_gadget_controller_number - support bcdDevice id convention
> >   * @gadget: the controller being driven
> > @@ -216,5 +222,7 @@ static inline int
> > usb_gadget_controller_number(struct usb_gadget *gadget) return 0x23;
> >         else if (gadget_is_cdns3(gadget))
> >                 return 0x24;
> > +       else if (gadget_is_max3420(gadget))
> > +               return 0x25;
> >         return -ENOENT;
> >  }
> > diff --git a/drivers/usb/gadget/max3420_udc.c
> > b/drivers/usb/gadget/max3420_udc.c new file mode 100644
> > index 0000000000..7267d482b8
> > --- /dev/null
> > +++ b/drivers/usb/gadget/max3420_udc.c
> > @@ -0,0 +1,875 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +
> > +#include <common.h>
> > +#include <linux/errno.h>
> > +#include <asm/gpio.h>
> > +#include <linux/list.h>
> > +#include <linux/usb/ch9.h>
> > +#include <linux/usb/gadget.h>
> > +#include <malloc.h>
> > +#include <spi.h>
> > +#include <dm.h>
> > +#include <g_dnl.h>
> > +
> > +#define MAX3420_MAX_EPS                4
> > +#define EP_MAX_PACKET          64  /* Same for all Endpoints */
> > +#define EPNAME_SIZE            16  /* Buffer size for endpoint
> > name */ +
> > +#define ACKSTAT                BIT(0)
> > +
> > +#define MAX3420_SPI_DIR_RD     0       /* read register from
> > MAX3420 */ +#define MAX3420_SPI_DIR_WR     1       /* write
> > register to MAX3420 */ +
> > +/* SPI commands: */
> > +#define MAX3420_SPI_DIR_SHIFT  1
> > +#define MAX3420_SPI_REG_SHIFT  3
> > +
> > +#define MAX3420_REG_EP0FIFO    0
> > +#define MAX3420_REG_EP1FIFO    1
> > +#define MAX3420_REG_EP2FIFO    2
> > +#define MAX3420_REG_EP3FIFO    3
> > +#define MAX3420_REG_SUDFIFO    4
> > +#define MAX3420_REG_EP0BC      5
> > +#define MAX3420_REG_EP1BC      6
> > +#define MAX3420_REG_EP2BC      7
> > +#define MAX3420_REG_EP3BC      8
> > +
> > +#define MAX3420_REG_EPSTALLS   9
> > +       #define bACKSTAT        BIT(6)
> > +       #define bSTLSTAT        BIT(5)
> > +       #define bSTLEP3IN       BIT(4)
> > +       #define bSTLEP2IN       BIT(3)
> > +       #define bSTLEP1OUT      BIT(2)
> > +       #define bSTLEP0OUT      BIT(1)
> > +       #define bSTLEP0IN       BIT(0)
> > +
> > +#define MAX3420_REG_CLRTOGS    10
> > +       #define bEP3DISAB       BIT(7)
> > +       #define bEP2DISAB       BIT(6)
> > +       #define bEP1DISAB       BIT(5)
> > +       #define bCTGEP3IN       BIT(4)
> > +       #define bCTGEP2IN       BIT(3)
> > +       #define bCTGEP1OUT      BIT(2)
> > +
> > +#define MAX3420_REG_EPIRQ      11
> > +#define MAX3420_REG_EPIEN      12
> > +       #define bSUDAVIRQ       BIT(5)
> > +       #define bIN3BAVIRQ      BIT(4)
> > +       #define bIN2BAVIRQ      BIT(3)
> > +       #define bOUT1DAVIRQ     BIT(2)
> > +       #define bOUT0DAVIRQ     BIT(1)
> > +       #define bIN0BAVIRQ      BIT(0)
> > +
> > +#define MAX3420_REG_USBIRQ     13
> > +#define MAX3420_REG_USBIEN     14
> > +       #define bOSCOKIRQ       BIT(0)
> > +       #define bRWUDNIRQ       BIT(1)
> > +       #define bBUSACTIRQ      BIT(2)
> > +       #define bURESIRQ        BIT(3)
> > +       #define bSUSPIRQ        BIT(4)
> > +       #define bNOVBUSIRQ      BIT(5)
> > +       #define bVBUSIRQ        BIT(6)
> > +       #define bURESDNIRQ      BIT(7)
> > +
> > +#define MAX3420_REG_USBCTL     15
> > +       #define bHOSCSTEN       BIT(7)
> > +       #define bVBGATE         BIT(6)
> > +       #define bCHIPRES        BIT(5)
> > +       #define bPWRDOWN        BIT(4)
> > +       #define bCONNECT        BIT(3)
> > +       #define bSIGRWU         BIT(2)
> > +
> > +#define MAX3420_REG_CPUCTL     16
> > +       #define bIE             BIT(0)
> > +
> > +#define MAX3420_REG_PINCTL     17
> > +       #define bEP3INAK        BIT(7)
> > +       #define bEP2INAK        BIT(6)
> > +       #define bEP0INAK        BIT(5)
> > +       #define bFDUPSPI        BIT(4)
> > +       #define bINTLEVEL       BIT(3)
> > +       #define bPOSINT         BIT(2)
> > +       #define bGPXB           BIT(1)
> > +       #define bGPXA           BIT(0)
> > +
> > +#define MAX3420_REG_REVISION   18
> > +
> > +#define MAX3420_REG_FNADDR     19
> > +       #define FNADDR_MASK     0x7f
> > +
> > +#define MAX3420_REG_IOPINS     20
> > +#define MAX3420_REG_IOPINS2    21
> > +#define MAX3420_REG_GPINIRQ    22
> > +#define MAX3420_REG_GPINIEN    23
> > +#define MAX3420_REG_GPINPOL    24
> > +#define MAX3420_REG_HIRQ       25
> > +#define MAX3420_REG_HIEN       26
> > +#define MAX3420_REG_MODE       27
> > +#define MAX3420_REG_PERADDR    28
> > +#define MAX3420_REG_HCTL       29
> > +#define MAX3420_REG_HXFR       30
> > +#define MAX3420_REG_HRSL       31
> > +

When I do look into drivers/usb/gadget/f_dfu.* the defines are placed
in the f_dfu.h file.

> > +#define field(val, bit)        ((val) << (bit))
> > +
> > +#define msleep(a)      udelay((a) * 1000)
> > +

Aren't those two above already defined in some *.h files?

> > +struct max3420_req {
> > +       struct usb_request usb_req;
> > +       struct list_head queue;
> > +       struct max3420_ep *ep;
> > +};
> > +
> > +struct max3420_ep {
> > +       struct max3420_udc *udc;
> > +       struct list_head queue;
> > +       char name[EPNAME_SIZE];
> > +       unsigned int maxpacket;
> > +       struct usb_ep ep_usb;
> > +       int halted;
> > +       int id;
> > +};
> > +
> > +struct max3420_udc {
> > +       struct max3420_ep ep[MAX3420_MAX_EPS];
> > +       struct usb_gadget_driver *driver;
> > +       bool vbus_active, softconnect;
> > +       struct usb_ctrlrequest setup;
> > +       struct max3420_req ep0req;
> > +       struct usb_gadget gadget;
> > +       struct spi_slave *slave;
> > +       struct udevice *dev;
> > +       u8 ep0buf[64];
> > +       int remote_wkp;
> > +       bool suspended;
> > +};
> > +
> > +#define to_max3420_req(r)      container_of((r), struct
> > max3420_req, usb_req) +#define to_max3420_ep(e)
> > container_of((e), struct max3420_ep, ep_usb) +#define to_udc(g)
> >          container_of((g), struct max3420_udc, gadget) +
> > +static void spi_ack_ctrl(struct max3420_udc *udc)
> > +{
> > +       struct spi_slave *slave = udc->slave;
> > +       u8 txdata[1];
> > +
> > +       txdata[0] = ACKSTAT;
> > +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_ONCE);
> > +}
> > +
> > +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int actstat)
> > +{
> > +       struct spi_slave *slave = udc->slave;
> > +       u8 txdata[2], rxdata[2];
> > +
> > +       txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
> > +                       field(MAX3420_SPI_DIR_RD,
> > MAX3420_SPI_DIR_SHIFT) |
> > +                       (actstat ? ACKSTAT : 0);
> > +
> > +       rxdata[0] = 0;
> > +       rxdata[1] = 0;
> > +       spi_xfer(slave, 2 * 8, txdata, rxdata, SPI_XFER_ONCE);
> > +
> > +       return rxdata[1];
> > +}
> > +
> > +static u8 spi_rd8(struct max3420_udc *udc, u8 reg)
> > +{
> > +       return spi_rd8_ack(udc, reg, 0);
> > +}
> > +
> > +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val,
> > int actstat) +{
> > +       struct spi_slave *slave = udc->slave;
> > +       u8 txdata[2];
> > +
> > +       txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
> > +                       field(MAX3420_SPI_DIR_WR,
> > MAX3420_SPI_DIR_SHIFT) |
> > +                       (actstat ? ACKSTAT : 0);
> > +       txdata[1] = val;
> > +
> > +       spi_xfer(slave, 2 * 8, txdata, NULL, SPI_XFER_ONCE);
> > +}

2 * 8 -> sizeof(txdata) ?

> > +
> > +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val)
> > +{
> > +       spi_wr8_ack(udc, reg, val, 0);
> > +}
> > +
> > +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf,
> > u8 len) +{
> > +       struct spi_slave *slave = udc->slave;
> > +       u8 txdata[1];
> > +
> > +       txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
> > +                        field(MAX3420_SPI_DIR_RD,
> > MAX3420_SPI_DIR_SHIFT)); +
> > +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);

I think that 1 * 8 shall be replaced with just 8.

> > +       spi_xfer(slave, len * 8, NULL, buf, SPI_XFER_END);
> > +}
> > +
> > +static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf,
> > u8 len) +{
> > +       struct spi_slave *slave = udc->slave;
> > +       u8 txdata[1];
> > +
> > +       txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
> > +                        field(MAX3420_SPI_DIR_WR,
> > MAX3420_SPI_DIR_SHIFT)); +
> > +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);
> > +       spi_xfer(slave, len * 8, buf, NULL, SPI_XFER_END);
> > +}
> > +
> > +/* 0 if not-connected */
> > +int g_dnl_board_usb_cable_connected(void)
> > +{
> > +       return 1;
> > +}
> > +
> > +static void spi_max3420_enable(struct max3420_ep *ep, int enable)
> > +{
> > +       struct max3420_udc *udc = ep->udc;
> > +       u8 epdis, epien;
> > +
> > +       if (ep->id == 0)
> > +               return;
> > +
> > +       epien = spi_rd8(udc, MAX3420_REG_EPIEN);
> > +       epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS);
> > +
> > +       if (enable) {
> > +               epdis &= ~BIT(ep->id + 4);
> > +               epien |= BIT(ep->id + 1);
> > +       } else {
> > +               epdis |= BIT(ep->id + 4);
> > +               epien &= ~BIT(ep->id + 1);
> > +       }
> > +
> > +       spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis);
> > +       spi_wr8(udc, MAX3420_REG_EPIEN, epien);
> > +}
> > +
> > +static int
> > +max3420_ep_enable(struct usb_ep *_ep,
> > +                 const struct usb_endpoint_descriptor *desc)
> > +{
> > +       struct max3420_ep *ep = to_max3420_ep(_ep);
> > +
> > +       _ep->desc = desc;
> > +       _ep->maxpacket = usb_endpoint_maxp(desc) & 0x7ff;
> > +
> > +       spi_max3420_enable(ep, 1);
> > +
> > +       return 0;
> > +}
> > +
> > +static void max3420_req_done(struct max3420_req *req, int status)
> > +{
> > +       struct max3420_ep *ep = req->ep;
> > +
> > +       if (req->usb_req.status == -EINPROGRESS)
> > +               req->usb_req.status = status;
> > +       else
> > +               status = req->usb_req.status;
> > +
> > +       if (status && status != -ESHUTDOWN)
> > +               dev_err(ep->udc->dev, "%s done %p, status %d\n",
> > +                       ep->ep_usb.name, req, status);
> > +
> > +       if (req->usb_req.complete)
> > +               req->usb_req.complete(&ep->ep_usb, &req->usb_req);
> > +}
> > +
> > +static void max3420_ep_nuke(struct max3420_ep *ep, int status)
> > +{
> > +       struct max3420_req *req, *r;
> > +
> > +       list_for_each_entry_safe(req, r, &ep->queue, queue) {
> > +               list_del_init(&req->queue);
> > +               max3420_req_done(req, status);
> > +       }
> > +}
> > +
> > +static int max3420_ep_disable(struct usb_ep *_ep)
> > +{
> > +       struct max3420_ep *ep = to_max3420_ep(_ep);
> > +
> > +       _ep->desc = NULL;
> > +       max3420_ep_nuke(ep, -ESHUTDOWN);
> > +       spi_max3420_enable(ep, 0);
> > +
> > +       return 0;
> > +}
> > +
> > +static struct usb_request *
> > +max3420_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
> > +{
> > +       struct max3420_ep *ep = to_max3420_ep(_ep);
> > +       struct max3420_req *req = kzalloc(sizeof(*req), gfp_flags);
> > +
> > +       if (!req)
> > +               return NULL;
> > +
> > +       req->ep = ep;
> > +       INIT_LIST_HEAD(&req->queue);
> > +
> > +       return &req->usb_req;
> > +}
> > +
> > +static void
> > +max3420_ep_free_request(struct usb_ep *_ep, struct usb_request
> > *_req) +{
> > +       kfree(to_max3420_req(_req));
> > +}
> > +
> > +static int
> > +max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
> > gfp_t gfp_flags) +{
> > +       struct max3420_req *req = to_max3420_req(_req);
> > +       struct max3420_ep *ep = to_max3420_ep(_ep);
> > +
> > +       _req->status = -EINPROGRESS;
> > +       _req->actual = 0;
> > +       list_add_tail(&req->queue, &ep->queue);
> > +
> > +       return 0;
> > +}
> > +
> > +static int max3420_ep_dequeue(struct usb_ep *_ep, struct
> > usb_request *_req) +{
> > +       struct max3420_req *req = to_max3420_req(_req);
> > +
> > +       list_del_init(&req->queue);
> > +       max3420_req_done(req, -ECONNRESET);
> > +
> > +       return 0;
> > +}
> > +
> > +static int max3420_ep_set_halt(struct usb_ep *_ep, int halt)
> > +{
> > +       struct max3420_ep *ep = to_max3420_ep(_ep);
> > +       struct max3420_udc *udc = ep->udc;
> > +       u8 epstalls;
> > +
> > +       if (ep->id == 0) /* can't stall EP0 */
> > +               return 0;
> > +
> > +       epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS);
> > +       if (halt) {
> > +               ep->halted = 1;
> > +               epstalls |= BIT(ep->id + 1);
> > +       } else {
> > +               u8 clrtogs;
> > +
> > +               ep->halted = 0;
> > +               epstalls &= ~BIT(ep->id + 1);
> > +               clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS);
> > +               clrtogs |= BIT(ep->id + 1);
> > +               spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs);
> > +       }
> > +       spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | bACKSTAT);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct usb_ep_ops max3420_ep_ops = {
> > +       .enable         = max3420_ep_enable,
> > +       .disable        = max3420_ep_disable,
> > +       .alloc_request  = max3420_ep_alloc_request,
> > +       .free_request   = max3420_ep_free_request,
> > +       .queue          = max3420_ep_queue,
> > +       .dequeue        = max3420_ep_dequeue,
> > +       .set_halt       = max3420_ep_set_halt,
> > +};
> > +
> > +static void __max3420_stop(struct max3420_udc *udc)
> > +{
> > +       u8 val;
> > +
> > +       /* Disable IRQ to CPU */
> > +       spi_wr8(udc, MAX3420_REG_CPUCTL, 0);
> > +
> > +       val = spi_rd8(udc, MAX3420_REG_USBCTL);
> > +       val |= bPWRDOWN;
> > +       val |= bHOSCSTEN;
> > +       spi_wr8(udc, MAX3420_REG_USBCTL, val);
> > +}
> > +
> > +static void __max3420_start(struct max3420_udc *udc)
> > +{
> > +       u8 val;
> > +
> > +       /* configure SPI */
> > +       spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI);
> > +
> > +       /* Chip Reset */
> > +       spi_wr8(udc, MAX3420_REG_USBCTL, bCHIPRES);
> > +       msleep(5);
> > +       spi_wr8(udc, MAX3420_REG_USBCTL, 0);
> > +
> > +       /* Poll for OSC to stabilize */
> > +       while (1) {
> > +               val = spi_rd8(udc, MAX3420_REG_USBIRQ);
> > +               if (val & bOSCOKIRQ)
> > +                       break;
> > +               cond_resched();
> > +       }
> > +
> > +       /* Enable PULL-UP only when Vbus detected */
> > +       val = spi_rd8(udc, MAX3420_REG_USBCTL);
> > +       val |= bVBGATE | bCONNECT;
> > +       spi_wr8(udc, MAX3420_REG_USBCTL, val);
> > +
> > +       val = bURESDNIRQ | bURESIRQ;
> > +       spi_wr8(udc, MAX3420_REG_USBIEN, val);
> > +
> > +       /* Enable only EP0 interrupts */
> > +       val = bIN0BAVIRQ | bOUT0DAVIRQ | bSUDAVIRQ;
> > +       spi_wr8(udc, MAX3420_REG_EPIEN, val);
> > +
> > +       /* Enable IRQ to CPU */
> > +       spi_wr8(udc, MAX3420_REG_CPUCTL, bIE);
> > +}
> > +
> > +static int max3420_udc_start(struct usb_gadget *gadget,
> > +                            struct usb_gadget_driver *driver)
> > +{
> > +       struct max3420_udc *udc = to_udc(gadget);
> > +       unsigned long flags;
> > +
> > +       udc->driver = driver;
> > +       udc->remote_wkp = 0;
> > +       udc->softconnect = true;
> > +
> > +       //if (udc->vbus_active)
> > +               __max3420_start(udc);
> > +

Please refactor the code and remove any lines started with //. 

> > +       return 0;
> > +}
> > +
> > +static int max3420_udc_stop(struct usb_gadget *gadget)
> > +{
> > +       struct max3420_udc *udc = to_udc(gadget);
> > +
> > +       udc->driver = NULL;
> > +       udc->softconnect = false;
> > +
> > +       __max3420_stop(udc);
> > +
> > +       return 0;
> > +}
> > +
> > +static int max3420_wakeup(struct usb_gadget *gadget)
> > +{
> > +       struct max3420_udc *udc = to_udc(gadget);
> > +       u8 usbctl;
> > +
> > +       /* Only if wakeup allowed by host */
> > +       if (!udc->remote_wkp || !udc->suspended)
> > +               return 0;
> > +
> > +       /* Set Remote-WkUp Signal*/

WkUp? -> Wakeup?

> > +       usbctl = spi_rd8(udc, MAX3420_REG_USBCTL);
> > +       usbctl |= bSIGRWU;
> > +       spi_wr8(udc, MAX3420_REG_USBCTL, usbctl);
> > +
> > +       msleep(5);
> > +
> > +       /* Clear Remote-WkUp Signal*/
> > +       usbctl = spi_rd8(udc, MAX3420_REG_USBCTL);
> > +       usbctl &= ~bSIGRWU;
> > +       spi_wr8(udc, MAX3420_REG_USBCTL, usbctl);
> > +
> > +       udc->suspended = false;
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct usb_gadget_ops max3420_udc_ops = {
> > +       .udc_start      = max3420_udc_start,
> > +       .udc_stop       = max3420_udc_stop,
> > +       .wakeup         = max3420_wakeup,
> > +};
> > +
> > +static struct usb_endpoint_descriptor ep0_desc = {
> > +       .bLength = USB_DT_ENDPOINT_SIZE,
> > +       .bDescriptorType = USB_DT_ENDPOINT,
> > +       .bEndpointAddress = USB_DIR_OUT,
> > +       .bmAttributes = USB_ENDPOINT_XFER_CONTROL,
> > +       .wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET),
> > +};
> > +
> > +static void max3420_getstatus(struct max3420_udc *udc)
> > +{
> > +       struct max3420_ep *ep;
> > +       u16 status = 0;
> > +
> > +       switch (udc->setup.bRequestType & USB_RECIP_MASK) {
> > +       case USB_RECIP_DEVICE:
> > +               /* Get device status */
> > +               status = 0 << USB_DEVICE_SELF_POWERED;
> > +               status |= (udc->remote_wkp <<
> > USB_DEVICE_REMOTE_WAKEUP);
> > +               break;
> > +       case USB_RECIP_INTERFACE:
> > +               if (udc->driver->setup(&udc->gadget, &udc->setup) <
> > 0)
> > +                       goto stall;
> > +               break;
> > +       case USB_RECIP_ENDPOINT:
> > +               ep = &udc->ep[udc->setup.wIndex &
> > USB_ENDPOINT_NUMBER_MASK];
> > +               if (ep->halted)
> > +                       status = 1 << USB_ENDPOINT_HALT;
> > +               break;
> > +       default:
> > +               goto stall;
> > +       }
> > +
> > +       status = cpu_to_le16(status);
> > +       spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2);
> > +       spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1);
> > +       return;
> > +stall:
> > +       dev_err(udc->dev, "Can't respond to getstatus request\n");
> > +       spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT |
> > bSTLSTAT); +}
> > +
> > +static void max3420_set_clear_feature(struct max3420_udc *udc)
> > +{
> > +       int set = udc->setup.bRequest == USB_REQ_SET_FEATURE;
> > +       struct max3420_ep *ep;
> > +       int id;
> > +
> > +       switch (udc->setup.bRequestType) {
> > +       case USB_RECIP_DEVICE:
> > +               if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP)
> > +                       break;
> > +
> > +               if (udc->setup.bRequest == USB_REQ_SET_FEATURE)
> > +                       udc->remote_wkp = 1;
> > +               else
> > +                       udc->remote_wkp = 0;
> > +
> > +               return spi_ack_ctrl(udc);
> > +
> > +       case USB_RECIP_ENDPOINT:
> > +               if (udc->setup.wValue != USB_ENDPOINT_HALT)
> > +                       break;
> > +
> > +               id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;
> > +               ep = &udc->ep[id];
> > +
> > +               max3420_ep_set_halt(&ep->ep_usb, set);
> > +               return;
> > +       default:
> > +               break;
> > +       }
> > +
> > +       dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n");
> > +       spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT |
> > bSTLSTAT); +}
> > +
> > +static void max3420_handle_setup(struct max3420_udc *udc)
> > +{
> > +       struct usb_ctrlrequest setup;
> > +       u8 addr;
> > +
> > +       spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8);
> > +
> > +       udc->setup = setup;
> > +       udc->setup.wValue = cpu_to_le16(setup.wValue);
> > +       udc->setup.wIndex = cpu_to_le16(setup.wIndex);
> > +       udc->setup.wLength = cpu_to_le16(setup.wLength);
> > +
> > +       switch (udc->setup.bRequest) {
> > +       case USB_REQ_GET_STATUS:
> > +               /* Data+Status phase form udc */
> > +               if ((udc->setup.bRequestType &
> > +                               (USB_DIR_IN | USB_TYPE_MASK)) !=
> > +                               (USB_DIR_IN | USB_TYPE_STANDARD)) {
> > +                       break;
> > +               }
> > +               return max3420_getstatus(udc);
> > +       case USB_REQ_SET_ADDRESS:
> > +               /* Status phase from udc */
> > +               if (udc->setup.bRequestType != (USB_DIR_OUT |
> > +                               USB_TYPE_STANDARD |
> > USB_RECIP_DEVICE))
> > +                       break;
> > +               addr = spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1);
> > +               dev_dbg(udc->dev, "Assigned Address=%d/%d\n",
> > +                       udc->setup.wValue, addr);
> > +               return;
> > +       case USB_REQ_CLEAR_FEATURE:
> > +       case USB_REQ_SET_FEATURE:
> > +               /* Requests with no data phase, status phase from
> > udc */
> > +               if ((udc->setup.bRequestType & USB_TYPE_MASK)
> > +                               != USB_TYPE_STANDARD)
> > +                       break;
> > +               return max3420_set_clear_feature(udc);
> > +       default:
> > +               break;
> > +       }
> > +
> > +       if (udc->driver->setup(&udc->gadget, &setup) < 0) {
> > +               /* Stall EP0 */
> > +               spi_wr8(udc, MAX3420_REG_EPSTALLS,
> > +                       bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
> > +       }
> > +}
> > +
> > +static int do_data(struct max3420_udc *udc, int ep_id, int in)
> > +{
> > +       struct max3420_ep *ep = &udc->ep[ep_id];
> > +       struct max3420_req *req;
> > +       int done, length, psz;
> > +       void *buf;
> > +
> > +       if (list_empty(&ep->queue))
> > +               return 0;
> > +
> > +       req = list_first_entry(&ep->queue, struct max3420_req,
> > queue);
> > +       buf = req->usb_req.buf + req->usb_req.actual;
> > +
> > +       psz = ep->ep_usb.maxpacket;
> > +       length = req->usb_req.length - req->usb_req.actual;
> > +       length = min(length, psz);
> > +
> > +       if (length == 0) {
> > +               done = 1;
> > +               goto xfer_done;
> > +       }
> > +
> > +       done = 0;
> > +       if (in) {
> > +               spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf,
> > length);
> > +               spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length);
> > +               if (length < psz)
> > +                       done = 1;
> > +       } else {
> > +               psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id);
> > +               length = min(length, psz);
> > +               spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf,
> > length);
> > +               if (length < ep->ep_usb.maxpacket)
> > +                       done = 1;
> > +       }
> > +
> > +       req->usb_req.actual += length;
> > +
> > +       if (req->usb_req.actual == req->usb_req.length)
> > +               done = 1;
> > +
> > +xfer_done:
> > +       if (done) {
> > +               unsigned long flags;
> > +
> > +               list_del_init(&req->queue);
> > +
> > +               if (ep_id == 0)
> > +                       spi_ack_ctrl(udc);
> > +
> > +               max3420_req_done(req, 0);
> > +       }
> > +
> > +       return 1;
> > +}
> > +
> > +static int max3420_handle_irqs(struct max3420_udc *udc)
> > +{
> > +       u8 epien, epirq, usbirq, usbien, reg[4];
> > +       int ret = 0;
> > +
> > +       spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4);
> > +       epirq = reg[0];
> > +       epien = reg[1];
> > +       usbirq = reg[2];
> > +       usbien = reg[3];
> > +
> > +       usbirq &= usbien;
> > +       epirq &= epien;
> > +
> > +       if (epirq & bSUDAVIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_EPIRQ, bSUDAVIRQ);
> > +               max3420_handle_setup(udc);
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bVBUSIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bVBUSIRQ);
> > +               dev_dbg(udc->dev, "Cable plugged in\n");
> > +               g_dnl_clear_detach();
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bNOVBUSIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bNOVBUSIRQ);
> > +               dev_dbg(udc->dev, "Cable pulled out\n");
> > +               g_dnl_trigger_detach();
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bURESIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bURESIRQ);
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bURESDNIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bURESDNIRQ);
> > +               spi_wr8(udc, MAX3420_REG_USBIEN, bURESDNIRQ |
> > bURESIRQ);
> > +               spi_wr8(udc, MAX3420_REG_EPIEN, bSUDAVIRQ
> > +                       | bIN0BAVIRQ | bOUT0DAVIRQ);
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bSUSPIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bSUSPIRQ);
> > +               dev_dbg(udc->dev, "USB Suspend - Enter\n");
> > +               udc->suspended = true;
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bBUSACTIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bBUSACTIRQ);
> > +               dev_dbg(udc->dev, "USB Suspend - Exit\n");
> > +               udc->suspended = false;
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bRWUDNIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bRWUDNIRQ);
> > +               dev_dbg(udc->dev, "Asked Host to wakeup\n");
> > +               return 1;
> > +       }
> > +
> > +       if (usbirq & bOSCOKIRQ) {
> > +               spi_wr8(udc, MAX3420_REG_USBIRQ, bOSCOKIRQ);
> > +               dev_dbg(udc->dev, "Osc stabilized, start work\n");
> > +               return 1;
> > +       }
> > +
> > +       if (epirq & bOUT0DAVIRQ && do_data(udc, 0, 0)) {
> > +               spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT0DAVIRQ, 1);
> > +               ret = 1;
> > +       }
> > +
> > +       if (epirq & bIN0BAVIRQ && do_data(udc, 0, 1))
> > +               ret = 1;
> > +
> > +       if (epirq & bOUT1DAVIRQ && do_data(udc, 1, 0)) {
> > +               spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT1DAVIRQ, 1);
> > +               ret = 1;
> > +       }
> > +
> > +       if (epirq & bIN2BAVIRQ && do_data(udc, 2, 1))
> > +               ret = 1;
> > +
> > +       if (epirq & bIN3BAVIRQ && do_data(udc, 3, 1))
> > +               ret = 1;
> > +
> > +       return ret;
> > +}
> > +
> > +static int max3420_irq(struct max3420_udc *udc)
> > +{
> > +       /* needed ? */

Please make sure if the code is needed or not. 

> > +       do_data(udc, 0, 1); /* get done with the EP0 ZLP */
> > +
> > +       return max3420_handle_irqs(udc);
> > +}
> > +
> > +static void max3420_setup_eps(struct max3420_udc *udc)
> > +{
> > +       int i;
> > +
> > +       INIT_LIST_HEAD(&udc->gadget.ep_list);
> > +       INIT_LIST_HEAD(&udc->ep[0].ep_usb.ep_list);
> > +
> > +       for (i = 0; i < MAX3420_MAX_EPS; i++) {
> > +               struct max3420_ep *ep = &udc->ep[i];
> > +
> > +               INIT_LIST_HEAD(&ep->queue);
> > +
> > +               ep->id = i;
> > +               ep->udc = udc;
> > +               ep->ep_usb.ops = &max3420_ep_ops;
> > +               ep->ep_usb.name = ep->name;
> > +               ep->ep_usb.maxpacket = EP_MAX_PACKET;
> > +
> > +               if (i == 0) {
> > +                       ep->ep_usb.desc = &ep0_desc;
> > +                       snprintf(ep->name, EPNAME_SIZE, "ep0");
> > +                       continue;
> > +               }
> > +
> > +               list_add_tail(&ep->ep_usb.ep_list,
> > &udc->gadget.ep_list); +
> > +               if (i == 1)
> > +                       snprintf(ep->name, EPNAME_SIZE,
> > "ep1out-bulk");
> > +               else
> > +                       snprintf(ep->name, EPNAME_SIZE,
> > "ep%din-bulk", i);
> > +       };
> > +}
> > +
> > +static void max3420_setup_spi(struct max3420_udc *udc)
> > +{
> > +       u8 reg[8];
> > +
> > +       spi_claim_bus(udc->slave);
> > +       /* necessary? */

The same as above. If there are any quirks or doubts - please state
them in the verbose comment.

> > +       spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8);
> > +       /* configure SPI */
> > +       spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI);
> > +}
> > +
> > +int dm_usb_gadget_handle_interrupts(struct udevice *dev)
> > +{
> > +       struct max3420_udc *udc = dev_get_priv(dev);
> > +
> > +       return max3420_irq(udc);
> > +}
> > +
> > +static int max3420_udc_probe(struct udevice *dev)
> > +{
> > +       struct max3420_udc *udc = dev_get_priv(dev);
> > +       struct udevice *spid;
> > +
> > +       spi_get_bus_and_cs(3, 0, 10000000, SPI_MODE_3,
> > "spi_generic_drv",
> > +                          NULL, &spid, &udc->slave);

IMHO the bus number (3?), CS (0), speed (10000000) and mode
(SPI_MODE_3) shall be read from device tree.

Some other setups, which would use this IC, could reuse the code when
it is connected to other bus/CS/mode/speed.

> > +
> > +       udc->dev = dev;
> > +       udc->gadget.ep0 = &udc->ep[0].ep_usb;
> > +       udc->gadget.max_speed = USB_SPEED_FULL;
> > +       udc->gadget.speed = USB_SPEED_FULL;
> > +       udc->gadget.is_dualspeed = 0;
> > +       udc->gadget.ops = &max3420_udc_ops;
> > +       udc->gadget.name = "max3420-udc";
> > +
> > +       max3420_setup_eps(udc);
> > +       max3420_setup_spi(udc);
> > +
> > +       usb_add_gadget_udc((struct device *)dev, &udc->gadget);
> > +
> > +       return 0;
> > +}
> > +
> > +static int max3420_udc_remove(struct udevice *dev)
> > +{
> > +       struct max3420_udc *udc = dev_get_priv(dev);
> > +
> > +       usb_del_gadget_udc(&udc->gadget);
> > +
> > +       spi_release_bus(udc->slave);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct udevice_id max3420_ids[] = {
> > +       { .compatible = "maxim,max3421-udc" },
> > +       { }
> > +};
> > +
> > +U_BOOT_DRIVER(max3420_generic_udc) = {
> > +       .name = "max3420-udc",
> > +       .id = UCLASS_USB_GADGET_GENERIC,
> > +       .of_match = max3420_ids,
> > +       .probe = max3420_udc_probe,
> > +       .remove = max3420_udc_remove,
> > +       .priv_auto_alloc_size = sizeof(struct max3420_udc),
> > +};
> > --
> > 2.25.1
> >  

A few more remarks:

1. Is this code ported from the Linux kernel? If yes, please explicitly
state SHA1 and version from which it was ported.

2. Does this particular driver require any extra explanation or
description, which shall be put into ./doc/?

3. Please also follow comments from Marek, which have been sent in the
other mail.


Best regards,

Lukasz Majewski

--

DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-59 Fax: (+49)-8142-66989-80 Email: lukma at denx.de
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 488 bytes
Desc: OpenPGP digital signature
URL: <https://lists.denx.de/pipermail/u-boot/attachments/20200616/5a83be9f/attachment.sig>
Jassi Brar June 24, 2020, 4:53 a.m. UTC | #4
Hi Marek,

On Mon, Jun 15, 2020 at 6:49 PM Marek Vasut <marex at denx.de> wrote:

> >> +#define MAX3420_REG_MODE       27
> >> +#define MAX3420_REG_PERADDR    28
> >> +#define MAX3420_REG_HCTL       29
> >> +#define MAX3420_REG_HXFR       30
> >> +#define MAX3420_REG_HRSL       31
> >> +
> >> +#define field(val, bit)        ((val) << (bit))
>
> Is this GENMASK() ?
>
The 'val' is not a mask, otherwise we could use genmask.

> >> +#define msleep(a)      udelay((a) * 1000)
>
> Please use mdelay() .
>
Ok.

> >> +static int max3420_udc_start(struct usb_gadget *gadget,
> >> +                            struct usb_gadget_driver *driver)
> >> +{
> >> +       struct max3420_udc *udc = to_udc(gadget);
> >> +       unsigned long flags;
> >> +
> >> +       udc->driver = driver;
> >> +       udc->remote_wkp = 0;
> >> +       udc->softconnect = true;
> >> +
> >> +       //if (udc->vbus_active)
>
> Is this intended to be commented out ?
>
Yes.

Thank you!
Jassi Brar June 24, 2020, 5:09 a.m. UTC | #5
On Tue, Jun 16, 2020 at 2:29 AM Lukasz Majewski <lukma at denx.de> wrote:
>
> Hi Jassi,
>
> > ... a polite ping, Lukasz.
>
> The only excuse for so long lack of my response are my personal issues
> caused by the covid-19.
>
Sorry if I came across as pestering you. I hope all is well now.

.....
> > > +
> > > +#define MAX3420_REG_IOPINS     20
> > > +#define MAX3420_REG_IOPINS2    21
> > > +#define MAX3420_REG_GPINIRQ    22
> > > +#define MAX3420_REG_GPINIEN    23
> > > +#define MAX3420_REG_GPINPOL    24
> > > +#define MAX3420_REG_HIRQ       25
> > > +#define MAX3420_REG_HIEN       26
> > > +#define MAX3420_REG_MODE       27
> > > +#define MAX3420_REG_PERADDR    28
> > > +#define MAX3420_REG_HCTL       29
> > > +#define MAX3420_REG_HXFR       30
> > > +#define MAX3420_REG_HRSL       31
> > > +
>
> When I do look into drivers/usb/gadget/f_dfu.* the defines are placed
> in the f_dfu.h file.
>
One school of thought is to contain all code in one file, especially
when no other file should access it -- these defines are very max3420
specific and none else should need these.
But I am fine if you want them in a separate file.

> > > +#define field(val, bit)        ((val) << (bit))
> > > +
> > > +#define msleep(a)      udelay((a) * 1000)
> > > +
>
> Aren't those two above already defined in some *.h files?
>
I replaced msleep with mdelay as Marek suggested.
I couldn't find the simple shift op define as field.

> > > +
> > > +static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val,
> > > int actstat) +{
> > > +       struct spi_slave *slave = udc->slave;
> > > +       u8 txdata[2];
> > > +
> > > +       txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
> > > +                       field(MAX3420_SPI_DIR_WR,
> > > MAX3420_SPI_DIR_SHIFT) |
> > > +                       (actstat ? ACKSTAT : 0);
> > > +       txdata[1] = val;
> > > +
> > > +       spi_xfer(slave, 2 * 8, txdata, NULL, SPI_XFER_ONCE);
> > > +}
>
> 2 * 8 -> sizeof(txdata) ?
>
Sure.

> > > +
> > > +static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val)
> > > +{
> > > +       spi_wr8_ack(udc, reg, val, 0);
> > > +}
> > > +
> > > +static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf,
> > > u8 len) +{
> > > +       struct spi_slave *slave = udc->slave;
> > > +       u8 txdata[1];
> > > +
> > > +       txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
> > > +                        field(MAX3420_SPI_DIR_RD,
> > > MAX3420_SPI_DIR_SHIFT)); +
> > > +       spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);
>
> I think that 1 * 8 shall be replaced with just 8.
>
sizeof(txdata) is more consistent

> > > +
> > > +static int max3420_udc_start(struct usb_gadget *gadget,
> > > +                            struct usb_gadget_driver *driver)
> > > +{
> > > +       struct max3420_udc *udc = to_udc(gadget);
> > > +       unsigned long flags;
> > > +
> > > +       udc->driver = driver;
> > > +       udc->remote_wkp = 0;
> > > +       udc->softconnect = true;
> > > +
> > > +       //if (udc->vbus_active)
> > > +               __max3420_start(udc);
> > > +
>
> Please refactor the code and remove any lines started with //.
>
ok.


> > > +static int max3420_wakeup(struct usb_gadget *gadget)
> > > +{
> > > +       struct max3420_udc *udc = to_udc(gadget);
> > > +       u8 usbctl;
> > > +
> > > +       /* Only if wakeup allowed by host */
> > > +       if (!udc->remote_wkp || !udc->suspended)
> > > +               return 0;
> > > +
> > > +       /* Set Remote-WkUp Signal*/
>
> WkUp? -> Wakeup?
>
Ok.

> > > +
> > > +static int max3420_irq(struct max3420_udc *udc)
> > > +{
> > > +       /* needed ? */
>
> Please make sure if the code is needed or not.
>
Yup.

> > > +static void max3420_setup_spi(struct max3420_udc *udc)
> > > +{
> > > +       u8 reg[8];
> > > +
> > > +       spi_claim_bus(udc->slave);
> > > +       /* necessary? */
>
> The same as above. If there are any quirks or doubts - please state
> them in the verbose comment.
>
The spec doesn't say explicitly but my platform needed these. So I'll
keep them until someone comes with a reason not to.

> > > +
> > > +static int max3420_udc_probe(struct udevice *dev)
> > > +{
> > > +       struct max3420_udc *udc = dev_get_priv(dev);
> > > +       struct udevice *spid;
> > > +
> > > +       spi_get_bus_and_cs(3, 0, 10000000, SPI_MODE_3,
> > > "spi_generic_drv",
> > > +                          NULL, &spid, &udc->slave);
>
> IMHO the bus number (3?), CS (0), speed (10000000) and mode
> (SPI_MODE_3) shall be read from device tree.
>
> Some other setups, which would use this IC, could reuse the code when
> it is connected to other bus/CS/mode/speed.
>
Yes, of course.

>
> A few more remarks:
>
> 1. Is this code ported from the Linux kernel? If yes, please explicitly
> state SHA1 and version from which it was ported.
>
This code has no roots in the kernel.

> 2. Does this particular driver require any extra explanation or
> description, which shall be put into ./doc/?
>
We don't implement any platform specific feature, so it is generic udc driver.

> 3. Please also follow comments from Marek, which have been sent in the
> other mail.
>
Sure.

Thank you!
Marek Vasut June 24, 2020, 9:22 a.m. UTC | #6
On 6/24/20 6:53 AM, Jassi Brar wrote:
> Hi Marek,

Hi,

> On Mon, Jun 15, 2020 at 6:49 PM Marek Vasut <marex at denx.de> wrote:
> 
>>>> +#define MAX3420_REG_MODE       27
>>>> +#define MAX3420_REG_PERADDR    28
>>>> +#define MAX3420_REG_HCTL       29
>>>> +#define MAX3420_REG_HXFR       30
>>>> +#define MAX3420_REG_HRSL       31
>>>> +
>>>> +#define field(val, bit)        ((val) << (bit))
>>
>> Is this GENMASK() ?
>>
> The 'val' is not a mask, otherwise we could use genmask.

Ah, right. Isn't there already some such macro in include/linux/ ?

[...]

>>>> +static int max3420_udc_start(struct usb_gadget *gadget,
>>>> +                            struct usb_gadget_driver *driver)
>>>> +{
>>>> +       struct max3420_udc *udc = to_udc(gadget);
>>>> +       unsigned long flags;
>>>> +
>>>> +       udc->driver = driver;
>>>> +       udc->remote_wkp = 0;
>>>> +       udc->softconnect = true;
>>>> +
>>>> +       //if (udc->vbus_active)
>>
>> Is this intended to be commented out ?
>>
> Yes.

Why ?
Jassi Brar June 25, 2020, 5:22 p.m. UTC | #7
On Wed, Jun 24, 2020 at 4:24 AM Marek Vasut <marex at denx.de> wrote:
>
> On 6/24/20 6:53 AM, Jassi Brar wrote:
> > Hi Marek,
>
> Hi,
>
> > On Mon, Jun 15, 2020 at 6:49 PM Marek Vasut <marex at denx.de> wrote:
> >
> >>>> +#define MAX3420_REG_MODE       27
> >>>> +#define MAX3420_REG_PERADDR    28
> >>>> +#define MAX3420_REG_HCTL       29
> >>>> +#define MAX3420_REG_HXFR       30
> >>>> +#define MAX3420_REG_HRSL       31
> >>>> +
> >>>> +#define field(val, bit)        ((val) << (bit))
> >>
> >> Is this GENMASK() ?
> >>
> > The 'val' is not a mask, otherwise we could use genmask.
>
> Ah, right. Isn't there already some such macro in include/linux/ ?
>
I am not aware if there is.

> [...]
>
> >>>> +static int max3420_udc_start(struct usb_gadget *gadget,
> >>>> +                            struct usb_gadget_driver *driver)
> >>>> +{
> >>>> +       struct max3420_udc *udc = to_udc(gadget);
> >>>> +       unsigned long flags;
> >>>> +
> >>>> +       udc->driver = driver;
> >>>> +       udc->remote_wkp = 0;
> >>>> +       udc->softconnect = true;
> >>>> +
> >>>> +       //if (udc->vbus_active)
> >>
> >> Is this intended to be commented out ?
> >>
> > Yes.
>
> Why ?
>
That was remnant of debugging. The enumeration fails randomly if we
don't always reset the chip upon connection and the spec isn't clear
about it.

Thanks.
Sean Anderson July 29, 2020, 10:04 a.m. UTC | #8
On 6/24/20 1:09 AM, Jassi Brar wrote:
> On Tue, Jun 16, 2020 at 2:29 AM Lukasz Majewski <lukma@denx.de> wrote:

>>

>> Hi Jassi,

>>

>>> ... a polite ping, Lukasz.

>>

>> The only excuse for so long lack of my response are my personal issues

>> caused by the covid-19.

>>

> Sorry if I came across as pestering you. I hope all is well now.

> 

> .....

>>>> +

>>>> +#define MAX3420_REG_IOPINS     20

>>>> +#define MAX3420_REG_IOPINS2    21

>>>> +#define MAX3420_REG_GPINIRQ    22

>>>> +#define MAX3420_REG_GPINIEN    23

>>>> +#define MAX3420_REG_GPINPOL    24

>>>> +#define MAX3420_REG_HIRQ       25

>>>> +#define MAX3420_REG_HIEN       26

>>>> +#define MAX3420_REG_MODE       27

>>>> +#define MAX3420_REG_PERADDR    28

>>>> +#define MAX3420_REG_HCTL       29

>>>> +#define MAX3420_REG_HXFR       30

>>>> +#define MAX3420_REG_HRSL       31

>>>> +

>>

>> When I do look into drivers/usb/gadget/f_dfu.* the defines are placed

>> in the f_dfu.h file.

>>

> One school of thought is to contain all code in one file, especially

> when no other file should access it -- these defines are very max3420

> specific and none else should need these.

> But I am fine if you want them in a separate file.

> 

>>>> +#define field(val, bit)        ((val) << (bit))

>>>> +

>>>> +#define msleep(a)      udelay((a) * 1000)

>>>> +

>>

>> Aren't those two above already defined in some *.h files?

>>

> I replaced msleep with mdelay as Marek suggested.

> I couldn't find the simple shift op define as field.


Perhaps check out <linux/bitfield.h>? I believe FIELD_PREP is similar to
what you have there.

--Sean
Jassi Brar July 30, 2020, 1:53 a.m. UTC | #9
On Wed, Jul 29, 2020 at 5:04 AM Sean Anderson <seanga2@gmail.com> wrote:
>

> On 6/24/20 1:09 AM, Jassi Brar wrote:

> > On Tue, Jun 16, 2020 at 2:29 AM Lukasz Majewski <lukma@denx.de> wrote:

> >>

> >> Hi Jassi,

> >>

> >>> ... a polite ping, Lukasz.

> >>

> >> The only excuse for so long lack of my response are my personal issues

> >> caused by the covid-19.

> >>

> > Sorry if I came across as pestering you. I hope all is well now.

> >

> > .....

> >>>> +

> >>>> +#define MAX3420_REG_IOPINS     20

> >>>> +#define MAX3420_REG_IOPINS2    21

> >>>> +#define MAX3420_REG_GPINIRQ    22

> >>>> +#define MAX3420_REG_GPINIEN    23

> >>>> +#define MAX3420_REG_GPINPOL    24

> >>>> +#define MAX3420_REG_HIRQ       25

> >>>> +#define MAX3420_REG_HIEN       26

> >>>> +#define MAX3420_REG_MODE       27

> >>>> +#define MAX3420_REG_PERADDR    28

> >>>> +#define MAX3420_REG_HCTL       29

> >>>> +#define MAX3420_REG_HXFR       30

> >>>> +#define MAX3420_REG_HRSL       31

> >>>> +

> >>

> >> When I do look into drivers/usb/gadget/f_dfu.* the defines are placed

> >> in the f_dfu.h file.

> >>

> > One school of thought is to contain all code in one file, especially

> > when no other file should access it -- these defines are very max3420

> > specific and none else should need these.

> > But I am fine if you want them in a separate file.

> >

> >>>> +#define field(val, bit)        ((val) << (bit))

> >>>> +

> >>>> +#define msleep(a)      udelay((a) * 1000)

> >>>> +

> >>

> >> Aren't those two above already defined in some *.h files?

> >>

> > I replaced msleep with mdelay as Marek suggested.

> > I couldn't find the simple shift op define as field.

>

> Perhaps check out <linux/bitfield.h>? I believe FIELD_PREP is similar to

> what you have there.

>

One has to go two levels deep to see what FIELD_PREP, as compared to
the simple (value << shift) needed in the code.
But I am ok doing that, whatever floats the boat.

Cheers!
diff mbox series

Patch

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 46aa3fe954..7c0df5c264 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -105,6 +105,12 @@  config CI_UDC
 	  Say Y here to enable device controller functionality of the
 	  ChipIdea driver.
 
+config USB_GADGET_MAX3420
+	bool "MAX3420 USB Over SPI"
+	depends on DM_SPI
+	help
+	  MAX3420, from MAXIM, implements USB-over-SPI Full-Speed device controller.
+
 config USB_GADGET_VBUS_DRAW
 	int "Maximum VBUS Power usage (2-500 mA)"
 	range 2 500
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 70f3bf43e7..f560068b41 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -20,6 +20,7 @@  obj-$(CONFIG_USB_GADGET_BCM_UDC_OTG_PHY) += bcm_udc_otg_phy.o
 obj-$(CONFIG_USB_GADGET_DWC2_OTG) += dwc2_udc_otg.o
 obj-$(CONFIG_USB_GADGET_DWC2_OTG_PHY) += dwc2_udc_otg_phy.o
 obj-$(CONFIG_USB_GADGET_FOTG210) += fotg210.o
+obj-$(CONFIG_USB_GADGET_MAX3420) += max3420_udc.o
 obj-$(CONFIG_CI_UDC)	+= ci_udc.o
 ifndef CONFIG_SPL_BUILD
 obj-$(CONFIG_USB_GADGET_DOWNLOAD) += g_dnl.o
diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
index 91b0285244..587204cfb7 100644
--- a/drivers/usb/gadget/gadget_chips.h
+++ b/drivers/usb/gadget/gadget_chips.h
@@ -155,6 +155,12 @@ 
 #define gadget_is_cdns3(g)        0
 #endif
 
+#ifdef CONFIG_USB_GADGET_MAX3420
+#define gadget_is_max3420(g)        (!strcmp("max3420-udc", (g)->name))
+#else
+#define gadget_is_max3420(g)        0
+#endif
+
 /**
  * usb_gadget_controller_number - support bcdDevice id convention
  * @gadget: the controller being driven
@@ -216,5 +222,7 @@  static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
 		return 0x23;
 	else if (gadget_is_cdns3(gadget))
 		return 0x24;
+	else if (gadget_is_max3420(gadget))
+		return 0x25;
 	return -ENOENT;
 }
diff --git a/drivers/usb/gadget/max3420_udc.c b/drivers/usb/gadget/max3420_udc.c
new file mode 100644
index 0000000000..7267d482b8
--- /dev/null
+++ b/drivers/usb/gadget/max3420_udc.c
@@ -0,0 +1,875 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <common.h>
+#include <linux/errno.h>
+#include <asm/gpio.h>
+#include <linux/list.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <malloc.h>
+#include <spi.h>
+#include <dm.h>
+#include <g_dnl.h>
+
+#define MAX3420_MAX_EPS		4
+#define EP_MAX_PACKET		64  /* Same for all Endpoints */
+#define EPNAME_SIZE		16  /* Buffer size for endpoint name */
+
+#define ACKSTAT		BIT(0)
+
+#define MAX3420_SPI_DIR_RD	0	/* read register from MAX3420 */
+#define MAX3420_SPI_DIR_WR	1	/* write register to MAX3420 */
+
+/* SPI commands: */
+#define MAX3420_SPI_DIR_SHIFT	1
+#define MAX3420_SPI_REG_SHIFT	3
+
+#define MAX3420_REG_EP0FIFO	0
+#define MAX3420_REG_EP1FIFO	1
+#define MAX3420_REG_EP2FIFO	2
+#define MAX3420_REG_EP3FIFO	3
+#define MAX3420_REG_SUDFIFO	4
+#define MAX3420_REG_EP0BC	5
+#define MAX3420_REG_EP1BC	6
+#define MAX3420_REG_EP2BC	7
+#define MAX3420_REG_EP3BC	8
+
+#define MAX3420_REG_EPSTALLS	9
+	#define bACKSTAT	BIT(6)
+	#define bSTLSTAT	BIT(5)
+	#define bSTLEP3IN	BIT(4)
+	#define bSTLEP2IN	BIT(3)
+	#define bSTLEP1OUT	BIT(2)
+	#define bSTLEP0OUT	BIT(1)
+	#define bSTLEP0IN	BIT(0)
+
+#define MAX3420_REG_CLRTOGS	10
+	#define bEP3DISAB	BIT(7)
+	#define bEP2DISAB	BIT(6)
+	#define bEP1DISAB	BIT(5)
+	#define bCTGEP3IN	BIT(4)
+	#define bCTGEP2IN	BIT(3)
+	#define bCTGEP1OUT	BIT(2)
+
+#define MAX3420_REG_EPIRQ	11
+#define MAX3420_REG_EPIEN	12
+	#define bSUDAVIRQ	BIT(5)
+	#define bIN3BAVIRQ	BIT(4)
+	#define bIN2BAVIRQ	BIT(3)
+	#define bOUT1DAVIRQ	BIT(2)
+	#define bOUT0DAVIRQ	BIT(1)
+	#define bIN0BAVIRQ	BIT(0)
+
+#define MAX3420_REG_USBIRQ	13
+#define MAX3420_REG_USBIEN	14
+	#define bOSCOKIRQ	BIT(0)
+	#define bRWUDNIRQ	BIT(1)
+	#define bBUSACTIRQ	BIT(2)
+	#define bURESIRQ	BIT(3)
+	#define bSUSPIRQ	BIT(4)
+	#define bNOVBUSIRQ	BIT(5)
+	#define bVBUSIRQ	BIT(6)
+	#define bURESDNIRQ	BIT(7)
+
+#define MAX3420_REG_USBCTL	15
+	#define bHOSCSTEN	BIT(7)
+	#define bVBGATE		BIT(6)
+	#define bCHIPRES	BIT(5)
+	#define bPWRDOWN	BIT(4)
+	#define bCONNECT	BIT(3)
+	#define bSIGRWU		BIT(2)
+
+#define MAX3420_REG_CPUCTL	16
+	#define bIE		BIT(0)
+
+#define MAX3420_REG_PINCTL	17
+	#define bEP3INAK	BIT(7)
+	#define bEP2INAK	BIT(6)
+	#define bEP0INAK	BIT(5)
+	#define bFDUPSPI	BIT(4)
+	#define bINTLEVEL	BIT(3)
+	#define bPOSINT		BIT(2)
+	#define bGPXB		BIT(1)
+	#define bGPXA		BIT(0)
+
+#define MAX3420_REG_REVISION	18
+
+#define MAX3420_REG_FNADDR	19
+	#define FNADDR_MASK	0x7f
+
+#define MAX3420_REG_IOPINS	20
+#define MAX3420_REG_IOPINS2	21
+#define MAX3420_REG_GPINIRQ	22
+#define MAX3420_REG_GPINIEN	23
+#define MAX3420_REG_GPINPOL	24
+#define MAX3420_REG_HIRQ	25
+#define MAX3420_REG_HIEN	26
+#define MAX3420_REG_MODE	27
+#define MAX3420_REG_PERADDR	28
+#define MAX3420_REG_HCTL	29
+#define MAX3420_REG_HXFR	30
+#define MAX3420_REG_HRSL	31
+
+#define field(val, bit)	((val) << (bit))
+
+#define msleep(a)	udelay((a) * 1000)
+
+struct max3420_req {
+	struct usb_request usb_req;
+	struct list_head queue;
+	struct max3420_ep *ep;
+};
+
+struct max3420_ep {
+	struct max3420_udc *udc;
+	struct list_head queue;
+	char name[EPNAME_SIZE];
+	unsigned int maxpacket;
+	struct usb_ep ep_usb;
+	int halted;
+	int id;
+};
+
+struct max3420_udc {
+	struct max3420_ep ep[MAX3420_MAX_EPS];
+	struct usb_gadget_driver *driver;
+	bool vbus_active, softconnect;
+	struct usb_ctrlrequest setup;
+	struct max3420_req ep0req;
+	struct usb_gadget gadget;
+	struct spi_slave *slave;
+	struct udevice *dev;
+	u8 ep0buf[64];
+	int remote_wkp;
+	bool suspended;
+};
+
+#define to_max3420_req(r)	container_of((r), struct max3420_req, usb_req)
+#define to_max3420_ep(e)	container_of((e), struct max3420_ep, ep_usb)
+#define to_udc(g)		container_of((g), struct max3420_udc, gadget)
+
+static void spi_ack_ctrl(struct max3420_udc *udc)
+{
+	struct spi_slave *slave = udc->slave;
+	u8 txdata[1];
+
+	txdata[0] = ACKSTAT;
+	spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_ONCE);
+}
+
+static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int actstat)
+{
+	struct spi_slave *slave = udc->slave;
+	u8 txdata[2], rxdata[2];
+
+	txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
+			field(MAX3420_SPI_DIR_RD, MAX3420_SPI_DIR_SHIFT) |
+			(actstat ? ACKSTAT : 0);
+
+	rxdata[0] = 0;
+	rxdata[1] = 0;
+	spi_xfer(slave, 2 * 8, txdata, rxdata, SPI_XFER_ONCE);
+
+	return rxdata[1];
+}
+
+static u8 spi_rd8(struct max3420_udc *udc, u8 reg)
+{
+	return spi_rd8_ack(udc, reg, 0);
+}
+
+static void spi_wr8_ack(struct max3420_udc *udc, u8 reg, u8 val, int actstat)
+{
+	struct spi_slave *slave = udc->slave;
+	u8 txdata[2];
+
+	txdata[0] = field(reg, MAX3420_SPI_REG_SHIFT) |
+			field(MAX3420_SPI_DIR_WR, MAX3420_SPI_DIR_SHIFT) |
+			(actstat ? ACKSTAT : 0);
+	txdata[1] = val;
+
+	spi_xfer(slave, 2 * 8, txdata, NULL, SPI_XFER_ONCE);
+}
+
+static void spi_wr8(struct max3420_udc *udc, u8 reg, u8 val)
+{
+	spi_wr8_ack(udc, reg, val, 0);
+}
+
+static void spi_rd_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len)
+{
+	struct spi_slave *slave = udc->slave;
+	u8 txdata[1];
+
+	txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
+			 field(MAX3420_SPI_DIR_RD, MAX3420_SPI_DIR_SHIFT));
+
+	spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);
+	spi_xfer(slave, len * 8, NULL, buf, SPI_XFER_END);
+}
+
+static void spi_wr_buf(struct max3420_udc *udc, u8 reg, void *buf, u8 len)
+{
+	struct spi_slave *slave = udc->slave;
+	u8 txdata[1];
+
+	txdata[0] = (field(reg, MAX3420_SPI_REG_SHIFT) |
+			 field(MAX3420_SPI_DIR_WR, MAX3420_SPI_DIR_SHIFT));
+
+	spi_xfer(slave, 1 * 8, txdata, NULL, SPI_XFER_BEGIN);
+	spi_xfer(slave, len * 8, buf, NULL, SPI_XFER_END);
+}
+
+/* 0 if not-connected */
+int g_dnl_board_usb_cable_connected(void)
+{
+	return 1;
+}
+
+static void spi_max3420_enable(struct max3420_ep *ep, int enable)
+{
+	struct max3420_udc *udc = ep->udc;
+	u8 epdis, epien;
+
+	if (ep->id == 0)
+		return;
+
+	epien = spi_rd8(udc, MAX3420_REG_EPIEN);
+	epdis = spi_rd8(udc, MAX3420_REG_CLRTOGS);
+
+	if (enable) {
+		epdis &= ~BIT(ep->id + 4);
+		epien |= BIT(ep->id + 1);
+	} else {
+		epdis |= BIT(ep->id + 4);
+		epien &= ~BIT(ep->id + 1);
+	}
+
+	spi_wr8(udc, MAX3420_REG_CLRTOGS, epdis);
+	spi_wr8(udc, MAX3420_REG_EPIEN, epien);
+}
+
+static int
+max3420_ep_enable(struct usb_ep *_ep,
+		  const struct usb_endpoint_descriptor *desc)
+{
+	struct max3420_ep *ep = to_max3420_ep(_ep);
+
+	_ep->desc = desc;
+	_ep->maxpacket = usb_endpoint_maxp(desc) & 0x7ff;
+
+	spi_max3420_enable(ep, 1);
+
+	return 0;
+}
+
+static void max3420_req_done(struct max3420_req *req, int status)
+{
+	struct max3420_ep *ep = req->ep;
+
+	if (req->usb_req.status == -EINPROGRESS)
+		req->usb_req.status = status;
+	else
+		status = req->usb_req.status;
+
+	if (status && status != -ESHUTDOWN)
+		dev_err(ep->udc->dev, "%s done %p, status %d\n",
+			ep->ep_usb.name, req, status);
+
+	if (req->usb_req.complete)
+		req->usb_req.complete(&ep->ep_usb, &req->usb_req);
+}
+
+static void max3420_ep_nuke(struct max3420_ep *ep, int status)
+{
+	struct max3420_req *req, *r;
+
+	list_for_each_entry_safe(req, r, &ep->queue, queue) {
+		list_del_init(&req->queue);
+		max3420_req_done(req, status);
+	}
+}
+
+static int max3420_ep_disable(struct usb_ep *_ep)
+{
+	struct max3420_ep *ep = to_max3420_ep(_ep);
+
+	_ep->desc = NULL;
+	max3420_ep_nuke(ep, -ESHUTDOWN);
+	spi_max3420_enable(ep, 0);
+
+	return 0;
+}
+
+static struct usb_request *
+max3420_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
+{
+	struct max3420_ep *ep = to_max3420_ep(_ep);
+	struct max3420_req *req = kzalloc(sizeof(*req), gfp_flags);
+
+	if (!req)
+		return NULL;
+
+	req->ep = ep;
+	INIT_LIST_HEAD(&req->queue);
+
+	return &req->usb_req;
+}
+
+static void
+max3420_ep_free_request(struct usb_ep *_ep, struct usb_request *_req)
+{
+	kfree(to_max3420_req(_req));
+}
+
+static int
+max3420_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
+{
+	struct max3420_req *req = to_max3420_req(_req);
+	struct max3420_ep *ep = to_max3420_ep(_ep);
+
+	_req->status = -EINPROGRESS;
+	_req->actual = 0;
+	list_add_tail(&req->queue, &ep->queue);
+
+	return 0;
+}
+
+static int max3420_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+	struct max3420_req *req = to_max3420_req(_req);
+
+	list_del_init(&req->queue);
+	max3420_req_done(req, -ECONNRESET);
+
+	return 0;
+}
+
+static int max3420_ep_set_halt(struct usb_ep *_ep, int halt)
+{
+	struct max3420_ep *ep = to_max3420_ep(_ep);
+	struct max3420_udc *udc = ep->udc;
+	u8 epstalls;
+
+	if (ep->id == 0) /* can't stall EP0 */
+		return 0;
+
+	epstalls = spi_rd8(udc, MAX3420_REG_EPSTALLS);
+	if (halt) {
+		ep->halted = 1;
+		epstalls |= BIT(ep->id + 1);
+	} else {
+		u8 clrtogs;
+
+		ep->halted = 0;
+		epstalls &= ~BIT(ep->id + 1);
+		clrtogs = spi_rd8(udc, MAX3420_REG_CLRTOGS);
+		clrtogs |= BIT(ep->id + 1);
+		spi_wr8(udc, MAX3420_REG_CLRTOGS, clrtogs);
+	}
+	spi_wr8(udc, MAX3420_REG_EPSTALLS, epstalls | bACKSTAT);
+
+	return 0;
+}
+
+static const struct usb_ep_ops max3420_ep_ops = {
+	.enable		= max3420_ep_enable,
+	.disable	= max3420_ep_disable,
+	.alloc_request	= max3420_ep_alloc_request,
+	.free_request	= max3420_ep_free_request,
+	.queue		= max3420_ep_queue,
+	.dequeue	= max3420_ep_dequeue,
+	.set_halt	= max3420_ep_set_halt,
+};
+
+static void __max3420_stop(struct max3420_udc *udc)
+{
+	u8 val;
+
+	/* Disable IRQ to CPU */
+	spi_wr8(udc, MAX3420_REG_CPUCTL, 0);
+
+	val = spi_rd8(udc, MAX3420_REG_USBCTL);
+	val |= bPWRDOWN;
+	val |= bHOSCSTEN;
+	spi_wr8(udc, MAX3420_REG_USBCTL, val);
+}
+
+static void __max3420_start(struct max3420_udc *udc)
+{
+	u8 val;
+
+	/* configure SPI */
+	spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI);
+
+	/* Chip Reset */
+	spi_wr8(udc, MAX3420_REG_USBCTL, bCHIPRES);
+	msleep(5);
+	spi_wr8(udc, MAX3420_REG_USBCTL, 0);
+
+	/* Poll for OSC to stabilize */
+	while (1) {
+		val = spi_rd8(udc, MAX3420_REG_USBIRQ);
+		if (val & bOSCOKIRQ)
+			break;
+		cond_resched();
+	}
+
+	/* Enable PULL-UP only when Vbus detected */
+	val = spi_rd8(udc, MAX3420_REG_USBCTL);
+	val |= bVBGATE | bCONNECT;
+	spi_wr8(udc, MAX3420_REG_USBCTL, val);
+
+	val = bURESDNIRQ | bURESIRQ;
+	spi_wr8(udc, MAX3420_REG_USBIEN, val);
+
+	/* Enable only EP0 interrupts */
+	val = bIN0BAVIRQ | bOUT0DAVIRQ | bSUDAVIRQ;
+	spi_wr8(udc, MAX3420_REG_EPIEN, val);
+
+	/* Enable IRQ to CPU */
+	spi_wr8(udc, MAX3420_REG_CPUCTL, bIE);
+}
+
+static int max3420_udc_start(struct usb_gadget *gadget,
+			     struct usb_gadget_driver *driver)
+{
+	struct max3420_udc *udc = to_udc(gadget);
+	unsigned long flags;
+
+	udc->driver = driver;
+	udc->remote_wkp = 0;
+	udc->softconnect = true;
+
+	//if (udc->vbus_active)
+		__max3420_start(udc);
+
+	return 0;
+}
+
+static int max3420_udc_stop(struct usb_gadget *gadget)
+{
+	struct max3420_udc *udc = to_udc(gadget);
+
+	udc->driver = NULL;
+	udc->softconnect = false;
+
+	__max3420_stop(udc);
+
+	return 0;
+}
+
+static int max3420_wakeup(struct usb_gadget *gadget)
+{
+	struct max3420_udc *udc = to_udc(gadget);
+	u8 usbctl;
+
+	/* Only if wakeup allowed by host */
+	if (!udc->remote_wkp || !udc->suspended)
+		return 0;
+
+	/* Set Remote-WkUp Signal*/
+	usbctl = spi_rd8(udc, MAX3420_REG_USBCTL);
+	usbctl |= bSIGRWU;
+	spi_wr8(udc, MAX3420_REG_USBCTL, usbctl);
+
+	msleep(5);
+
+	/* Clear Remote-WkUp Signal*/
+	usbctl = spi_rd8(udc, MAX3420_REG_USBCTL);
+	usbctl &= ~bSIGRWU;
+	spi_wr8(udc, MAX3420_REG_USBCTL, usbctl);
+
+	udc->suspended = false;
+
+	return 0;
+}
+
+static const struct usb_gadget_ops max3420_udc_ops = {
+	.udc_start	= max3420_udc_start,
+	.udc_stop	= max3420_udc_stop,
+	.wakeup		= max3420_wakeup,
+};
+
+static struct usb_endpoint_descriptor ep0_desc = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_CONTROL,
+	.wMaxPacketSize = cpu_to_le16(EP_MAX_PACKET),
+};
+
+static void max3420_getstatus(struct max3420_udc *udc)
+{
+	struct max3420_ep *ep;
+	u16 status = 0;
+
+	switch (udc->setup.bRequestType & USB_RECIP_MASK) {
+	case USB_RECIP_DEVICE:
+		/* Get device status */
+		status = 0 << USB_DEVICE_SELF_POWERED;
+		status |= (udc->remote_wkp << USB_DEVICE_REMOTE_WAKEUP);
+		break;
+	case USB_RECIP_INTERFACE:
+		if (udc->driver->setup(&udc->gadget, &udc->setup) < 0)
+			goto stall;
+		break;
+	case USB_RECIP_ENDPOINT:
+		ep = &udc->ep[udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK];
+		if (ep->halted)
+			status = 1 << USB_ENDPOINT_HALT;
+		break;
+	default:
+		goto stall;
+	}
+
+	status = cpu_to_le16(status);
+	spi_wr_buf(udc, MAX3420_REG_EP0FIFO, &status, 2);
+	spi_wr8_ack(udc, MAX3420_REG_EP0BC, 2, 1);
+	return;
+stall:
+	dev_err(udc->dev, "Can't respond to getstatus request\n");
+	spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
+}
+
+static void max3420_set_clear_feature(struct max3420_udc *udc)
+{
+	int set = udc->setup.bRequest == USB_REQ_SET_FEATURE;
+	struct max3420_ep *ep;
+	int id;
+
+	switch (udc->setup.bRequestType) {
+	case USB_RECIP_DEVICE:
+		if (udc->setup.wValue != USB_DEVICE_REMOTE_WAKEUP)
+			break;
+
+		if (udc->setup.bRequest == USB_REQ_SET_FEATURE)
+			udc->remote_wkp = 1;
+		else
+			udc->remote_wkp = 0;
+
+		return spi_ack_ctrl(udc);
+
+	case USB_RECIP_ENDPOINT:
+		if (udc->setup.wValue != USB_ENDPOINT_HALT)
+			break;
+
+		id = udc->setup.wIndex & USB_ENDPOINT_NUMBER_MASK;
+		ep = &udc->ep[id];
+
+		max3420_ep_set_halt(&ep->ep_usb, set);
+		return;
+	default:
+		break;
+	}
+
+	dev_err(udc->dev, "Can't respond to SET/CLEAR FEATURE\n");
+	spi_wr8(udc, MAX3420_REG_EPSTALLS, bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
+}
+
+static void max3420_handle_setup(struct max3420_udc *udc)
+{
+	struct usb_ctrlrequest setup;
+	u8 addr;
+
+	spi_rd_buf(udc, MAX3420_REG_SUDFIFO, (void *)&setup, 8);
+
+	udc->setup = setup;
+	udc->setup.wValue = cpu_to_le16(setup.wValue);
+	udc->setup.wIndex = cpu_to_le16(setup.wIndex);
+	udc->setup.wLength = cpu_to_le16(setup.wLength);
+
+	switch (udc->setup.bRequest) {
+	case USB_REQ_GET_STATUS:
+		/* Data+Status phase form udc */
+		if ((udc->setup.bRequestType &
+				(USB_DIR_IN | USB_TYPE_MASK)) !=
+				(USB_DIR_IN | USB_TYPE_STANDARD)) {
+			break;
+		}
+		return max3420_getstatus(udc);
+	case USB_REQ_SET_ADDRESS:
+		/* Status phase from udc */
+		if (udc->setup.bRequestType != (USB_DIR_OUT |
+				USB_TYPE_STANDARD | USB_RECIP_DEVICE))
+			break;
+		addr = spi_rd8_ack(udc, MAX3420_REG_FNADDR, 1);
+		dev_dbg(udc->dev, "Assigned Address=%d/%d\n",
+			udc->setup.wValue, addr);
+		return;
+	case USB_REQ_CLEAR_FEATURE:
+	case USB_REQ_SET_FEATURE:
+		/* Requests with no data phase, status phase from udc */
+		if ((udc->setup.bRequestType & USB_TYPE_MASK)
+				!= USB_TYPE_STANDARD)
+			break;
+		return max3420_set_clear_feature(udc);
+	default:
+		break;
+	}
+
+	if (udc->driver->setup(&udc->gadget, &setup) < 0) {
+		/* Stall EP0 */
+		spi_wr8(udc, MAX3420_REG_EPSTALLS,
+			bSTLEP0IN | bSTLEP0OUT | bSTLSTAT);
+	}
+}
+
+static int do_data(struct max3420_udc *udc, int ep_id, int in)
+{
+	struct max3420_ep *ep = &udc->ep[ep_id];
+	struct max3420_req *req;
+	int done, length, psz;
+	void *buf;
+
+	if (list_empty(&ep->queue))
+		return 0;
+
+	req = list_first_entry(&ep->queue, struct max3420_req, queue);
+	buf = req->usb_req.buf + req->usb_req.actual;
+
+	psz = ep->ep_usb.maxpacket;
+	length = req->usb_req.length - req->usb_req.actual;
+	length = min(length, psz);
+
+	if (length == 0) {
+		done = 1;
+		goto xfer_done;
+	}
+
+	done = 0;
+	if (in) {
+		spi_wr_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length);
+		spi_wr8(udc, MAX3420_REG_EP0BC + ep_id, length);
+		if (length < psz)
+			done = 1;
+	} else {
+		psz = spi_rd8(udc, MAX3420_REG_EP0BC + ep_id);
+		length = min(length, psz);
+		spi_rd_buf(udc, MAX3420_REG_EP0FIFO + ep_id, buf, length);
+		if (length < ep->ep_usb.maxpacket)
+			done = 1;
+	}
+
+	req->usb_req.actual += length;
+
+	if (req->usb_req.actual == req->usb_req.length)
+		done = 1;
+
+xfer_done:
+	if (done) {
+		unsigned long flags;
+
+		list_del_init(&req->queue);
+
+		if (ep_id == 0)
+			spi_ack_ctrl(udc);
+
+		max3420_req_done(req, 0);
+	}
+
+	return 1;
+}
+
+static int max3420_handle_irqs(struct max3420_udc *udc)
+{
+	u8 epien, epirq, usbirq, usbien, reg[4];
+	int ret = 0;
+
+	spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 4);
+	epirq = reg[0];
+	epien = reg[1];
+	usbirq = reg[2];
+	usbien = reg[3];
+
+	usbirq &= usbien;
+	epirq &= epien;
+
+	if (epirq & bSUDAVIRQ) {
+		spi_wr8(udc, MAX3420_REG_EPIRQ, bSUDAVIRQ);
+		max3420_handle_setup(udc);
+		return 1;
+	}
+
+	if (usbirq & bVBUSIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bVBUSIRQ);
+		dev_dbg(udc->dev, "Cable plugged in\n");
+		g_dnl_clear_detach();
+		return 1;
+	}
+
+	if (usbirq & bNOVBUSIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bNOVBUSIRQ);
+		dev_dbg(udc->dev, "Cable pulled out\n");
+		g_dnl_trigger_detach();
+		return 1;
+	}
+
+	if (usbirq & bURESIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bURESIRQ);
+		return 1;
+	}
+
+	if (usbirq & bURESDNIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bURESDNIRQ);
+		spi_wr8(udc, MAX3420_REG_USBIEN, bURESDNIRQ | bURESIRQ);
+		spi_wr8(udc, MAX3420_REG_EPIEN, bSUDAVIRQ
+			| bIN0BAVIRQ | bOUT0DAVIRQ);
+		return 1;
+	}
+
+	if (usbirq & bSUSPIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bSUSPIRQ);
+		dev_dbg(udc->dev, "USB Suspend - Enter\n");
+		udc->suspended = true;
+		return 1;
+	}
+
+	if (usbirq & bBUSACTIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bBUSACTIRQ);
+		dev_dbg(udc->dev, "USB Suspend - Exit\n");
+		udc->suspended = false;
+		return 1;
+	}
+
+	if (usbirq & bRWUDNIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bRWUDNIRQ);
+		dev_dbg(udc->dev, "Asked Host to wakeup\n");
+		return 1;
+	}
+
+	if (usbirq & bOSCOKIRQ) {
+		spi_wr8(udc, MAX3420_REG_USBIRQ, bOSCOKIRQ);
+		dev_dbg(udc->dev, "Osc stabilized, start work\n");
+		return 1;
+	}
+
+	if (epirq & bOUT0DAVIRQ && do_data(udc, 0, 0)) {
+		spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT0DAVIRQ, 1);
+		ret = 1;
+	}
+
+	if (epirq & bIN0BAVIRQ && do_data(udc, 0, 1))
+		ret = 1;
+
+	if (epirq & bOUT1DAVIRQ && do_data(udc, 1, 0)) {
+		spi_wr8_ack(udc, MAX3420_REG_EPIRQ, bOUT1DAVIRQ, 1);
+		ret = 1;
+	}
+
+	if (epirq & bIN2BAVIRQ && do_data(udc, 2, 1))
+		ret = 1;
+
+	if (epirq & bIN3BAVIRQ && do_data(udc, 3, 1))
+		ret = 1;
+
+	return ret;
+}
+
+static int max3420_irq(struct max3420_udc *udc)
+{
+	/* needed ? */
+	do_data(udc, 0, 1); /* get done with the EP0 ZLP */
+
+	return max3420_handle_irqs(udc);
+}
+
+static void max3420_setup_eps(struct max3420_udc *udc)
+{
+	int i;
+
+	INIT_LIST_HEAD(&udc->gadget.ep_list);
+	INIT_LIST_HEAD(&udc->ep[0].ep_usb.ep_list);
+
+	for (i = 0; i < MAX3420_MAX_EPS; i++) {
+		struct max3420_ep *ep = &udc->ep[i];
+
+		INIT_LIST_HEAD(&ep->queue);
+
+		ep->id = i;
+		ep->udc = udc;
+		ep->ep_usb.ops = &max3420_ep_ops;
+		ep->ep_usb.name = ep->name;
+		ep->ep_usb.maxpacket = EP_MAX_PACKET;
+
+		if (i == 0) {
+			ep->ep_usb.desc = &ep0_desc;
+			snprintf(ep->name, EPNAME_SIZE, "ep0");
+			continue;
+		}
+
+		list_add_tail(&ep->ep_usb.ep_list, &udc->gadget.ep_list);
+
+		if (i == 1)
+			snprintf(ep->name, EPNAME_SIZE, "ep1out-bulk");
+		else
+			snprintf(ep->name, EPNAME_SIZE, "ep%din-bulk", i);
+	};
+}
+
+static void max3420_setup_spi(struct max3420_udc *udc)
+{
+	u8 reg[8];
+
+	spi_claim_bus(udc->slave);
+	/* necessary? */
+	spi_rd_buf(udc, MAX3420_REG_EPIRQ, reg, 8);
+	/* configure SPI */
+	spi_wr8(udc, MAX3420_REG_PINCTL, bFDUPSPI);
+}
+
+int dm_usb_gadget_handle_interrupts(struct udevice *dev)
+{
+	struct max3420_udc *udc = dev_get_priv(dev);
+
+	return max3420_irq(udc);
+}
+
+static int max3420_udc_probe(struct udevice *dev)
+{
+	struct max3420_udc *udc = dev_get_priv(dev);
+	struct udevice *spid;
+
+	spi_get_bus_and_cs(3, 0, 10000000, SPI_MODE_3, "spi_generic_drv",
+			   NULL, &spid, &udc->slave);
+
+	udc->dev = dev;
+	udc->gadget.ep0 = &udc->ep[0].ep_usb;
+	udc->gadget.max_speed = USB_SPEED_FULL;
+	udc->gadget.speed = USB_SPEED_FULL;
+	udc->gadget.is_dualspeed = 0;
+	udc->gadget.ops = &max3420_udc_ops;
+	udc->gadget.name = "max3420-udc";
+
+	max3420_setup_eps(udc);
+	max3420_setup_spi(udc);
+
+	usb_add_gadget_udc((struct device *)dev, &udc->gadget);
+
+	return 0;
+}
+
+static int max3420_udc_remove(struct udevice *dev)
+{
+	struct max3420_udc *udc = dev_get_priv(dev);
+
+	usb_del_gadget_udc(&udc->gadget);
+
+	spi_release_bus(udc->slave);
+
+	return 0;
+}
+
+static const struct udevice_id max3420_ids[] = {
+	{ .compatible = "maxim,max3421-udc" },
+	{ }
+};
+
+U_BOOT_DRIVER(max3420_generic_udc) = {
+	.name = "max3420-udc",
+	.id = UCLASS_USB_GADGET_GENERIC,
+	.of_match = max3420_ids,
+	.probe = max3420_udc_probe,
+	.remove = max3420_udc_remove,
+	.priv_auto_alloc_size = sizeof(struct max3420_udc),
+};