Message ID | 20200730015127.67394-1-jassisinghbrar@gmail.com |
---|---|
State | New |
Headers | show |
Series | [PATCHv3] usb: max3420: add the gadget driver | expand |
.... a polite ping ... On Wed, Jul 29, 2020 at 8:51 PM <jassisinghbrar@gmail.com> wrote: > > From: Jassi Brar <jaswinder.singh@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@linaro.org> > --- > Changes since v2: > - used FIELD_PREP instead of simple left shift operation > --- > 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..b38b9dc68f > --- /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 <linux/delay.h> > +#include <asm/gpio.h> > +#include <linux/list.h> > +#include <linux/bitfield.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 MAX3420_SPI_DIR_RD 0 /* read register from MAX3420 */ > +#define MAX3420_SPI_DIR_WR 1 /* write register to MAX3420 */ > + > +/* SPI commands: */ > +#define MAX3420_SPI_ACK_MASK BIT(0) > +#define MAX3420_SPI_DIR_MASK BIT(1) > +#define MAX3420_SPI_REG_MASK GENMASK(7, 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 > + > +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 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] = FIELD_PREP(MAX3420_SPI_ACK_MASK, 1); > + spi_xfer(slave, sizeof(txdata), txdata, NULL, SPI_XFER_ONCE); > +} > + > +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int ackstat) > +{ > + struct spi_slave *slave = udc->slave; > + u8 txdata[2], rxdata[2]; > + > + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | > + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_RD) | > + FIELD_PREP(MAX3420_SPI_ACK_MASK, ackstat ? 1 : 0); > + > + rxdata[0] = 0; > + rxdata[1] = 0; > + spi_xfer(slave, sizeof(txdata), 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 ackstat) > +{ > + struct spi_slave *slave = udc->slave; > + u8 txdata[2]; > + > + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | > + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_WR) | > + FIELD_PREP(MAX3420_SPI_ACK_MASK, ackstat ? 1 : 0); > + txdata[1] = val; > + > + spi_xfer(slave, sizeof(txdata), 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_PREP(MAX3420_SPI_REG_MASK, reg) | > + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_RD); > + > + spi_xfer(slave, sizeof(txdata), 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_PREP(MAX3420_SPI_REG_MASK, reg) | > + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_WR); > + > + spi_xfer(slave, sizeof(txdata), 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); > + mdelay(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); > + > + udc->driver = driver; > + udc->remote_wkp = 0; > + udc->softconnect = true; > + > + __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-Wakeup Signal*/ > + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); > + usbctl |= bSIGRWU; > + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); > + > + mdelay(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) { > + 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) > +{ > + 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); > + 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 dm_spi_slave_platdata *slave_pdata; > + struct udevice *bus = dev->parent; > + int busnum = bus->seq; > + unsigned int cs; > + uint speed, mode; > + struct udevice *spid; > + > + slave_pdata = dev_get_parent_platdata(dev); > + cs = slave_pdata->cs; > + speed = slave_pdata->max_hz; > + mode = slave_pdata->mode; > + spi_get_bus_and_cs(busnum, cs, speed, mode, "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 >
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..b38b9dc68f --- /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 <linux/delay.h> +#include <asm/gpio.h> +#include <linux/list.h> +#include <linux/bitfield.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 MAX3420_SPI_DIR_RD 0 /* read register from MAX3420 */ +#define MAX3420_SPI_DIR_WR 1 /* write register to MAX3420 */ + +/* SPI commands: */ +#define MAX3420_SPI_ACK_MASK BIT(0) +#define MAX3420_SPI_DIR_MASK BIT(1) +#define MAX3420_SPI_REG_MASK GENMASK(7, 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 + +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 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] = FIELD_PREP(MAX3420_SPI_ACK_MASK, 1); + spi_xfer(slave, sizeof(txdata), txdata, NULL, SPI_XFER_ONCE); +} + +static u8 spi_rd8_ack(struct max3420_udc *udc, u8 reg, int ackstat) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[2], rxdata[2]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_RD) | + FIELD_PREP(MAX3420_SPI_ACK_MASK, ackstat ? 1 : 0); + + rxdata[0] = 0; + rxdata[1] = 0; + spi_xfer(slave, sizeof(txdata), 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 ackstat) +{ + struct spi_slave *slave = udc->slave; + u8 txdata[2]; + + txdata[0] = FIELD_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_WR) | + FIELD_PREP(MAX3420_SPI_ACK_MASK, ackstat ? 1 : 0); + txdata[1] = val; + + spi_xfer(slave, sizeof(txdata), 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_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_RD); + + spi_xfer(slave, sizeof(txdata), 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_PREP(MAX3420_SPI_REG_MASK, reg) | + FIELD_PREP(MAX3420_SPI_DIR_MASK, MAX3420_SPI_DIR_WR); + + spi_xfer(slave, sizeof(txdata), 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); + mdelay(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); + + udc->driver = driver; + udc->remote_wkp = 0; + udc->softconnect = true; + + __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-Wakeup Signal*/ + usbctl = spi_rd8(udc, MAX3420_REG_USBCTL); + usbctl |= bSIGRWU; + spi_wr8(udc, MAX3420_REG_USBCTL, usbctl); + + mdelay(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) { + 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) +{ + 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); + 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 dm_spi_slave_platdata *slave_pdata; + struct udevice *bus = dev->parent; + int busnum = bus->seq; + unsigned int cs; + uint speed, mode; + struct udevice *spid; + + slave_pdata = dev_get_parent_platdata(dev); + cs = slave_pdata->cs; + speed = slave_pdata->max_hz; + mode = slave_pdata->mode; + spi_get_bus_and_cs(busnum, cs, speed, mode, "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), +};