[PATCHv3] usb: max3420: add the gadget driver
Jassi Brar
jassisinghbrar at gmail.com
Wed Aug 19 03:11:15 CEST 2020
.... a polite ping ...
On Wed, Jul 29, 2020 at 8:51 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>
> ---
> 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
>
More information about the U-Boot
mailing list