|  | /* | 
|  | * Copyright (c) 2018 Google LLC. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT atmel_sam0_usb | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(usb_dc_sam0); | 
|  |  | 
|  | #include <usb/usb_device.h> | 
|  | #include <soc.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #define NVM_USB_PAD_TRANSN_POS 45 | 
|  | #define NVM_USB_PAD_TRANSN_SIZE 5 | 
|  | #define NVM_USB_PAD_TRANSP_POS 50 | 
|  | #define NVM_USB_PAD_TRANSP_SIZE 5 | 
|  | #define NVM_USB_PAD_TRIM_POS 55 | 
|  | #define NVM_USB_PAD_TRIM_SIZE 3 | 
|  |  | 
|  | #define USB_SAM0_IN_EP 0x80 | 
|  |  | 
|  | #define REGS ((Usb *)DT_INST_REG_ADDR(0)) | 
|  | #define USB_NUM_ENDPOINTS DT_INST_PROP(0, num_bidir_endpoints) | 
|  |  | 
|  | /* The endpoint size stored in USB.PCKSIZE.SIZE */ | 
|  | enum usb_sam0_pcksize_size { | 
|  | USB_SAM0_PCKSIZE_SIZE_8 = 0, | 
|  | USB_SAM0_PCKSIZE_SIZE_16, | 
|  | USB_SAM0_PCKSIZE_SIZE_32, | 
|  | USB_SAM0_PCKSIZE_SIZE_64, | 
|  | USB_SAM0_PCKSIZE_SIZE_128, | 
|  | USB_SAM0_PCKSIZE_SIZE_256, | 
|  | USB_SAM0_PCKSIZE_SIZE_512, | 
|  | USB_SAM0_PCKSIZE_SIZE_1023, | 
|  | }; | 
|  |  | 
|  | static const uint16_t usb_sam0_pcksize_bytes[] = { | 
|  | [USB_SAM0_PCKSIZE_SIZE_8] = 8, | 
|  | [USB_SAM0_PCKSIZE_SIZE_16] = 16, | 
|  | [USB_SAM0_PCKSIZE_SIZE_32] = 32, | 
|  | [USB_SAM0_PCKSIZE_SIZE_64] = 64, | 
|  | [USB_SAM0_PCKSIZE_SIZE_128] = 128, | 
|  | [USB_SAM0_PCKSIZE_SIZE_256] = 256, | 
|  | [USB_SAM0_PCKSIZE_SIZE_512] = 512, | 
|  | [USB_SAM0_PCKSIZE_SIZE_1023] = 1023, | 
|  | }; | 
|  |  | 
|  | BUILD_ASSERT(ARRAY_SIZE(usb_sam0_pcksize_bytes) == 8); | 
|  |  | 
|  | struct usb_sam0_data { | 
|  | UsbDeviceDescriptor descriptors[USB_NUM_ENDPOINTS]; | 
|  |  | 
|  | usb_dc_status_callback cb; | 
|  | usb_dc_ep_callback ep_cb[2][USB_NUM_ENDPOINTS]; | 
|  |  | 
|  | uint8_t addr; | 
|  | uint32_t out_at; | 
|  | }; | 
|  |  | 
|  | static struct usb_sam0_data usb_sam0_data_0; | 
|  |  | 
|  | static struct usb_sam0_data *usb_sam0_get_data(void) | 
|  | { | 
|  | return &usb_sam0_data_0; | 
|  | } | 
|  |  | 
|  | /* Handles interrupts on an endpoint */ | 
|  | static void usb_sam0_ep_isr(uint8_t ep) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep]; | 
|  | uint32_t intflag = endpoint->EPINTFLAG.reg; | 
|  |  | 
|  | endpoint->EPINTFLAG.reg = intflag; | 
|  |  | 
|  | if ((intflag & USB_DEVICE_EPINTFLAG_RXSTP) != 0U) { | 
|  | /* Setup */ | 
|  | data->ep_cb[0][ep](ep, USB_DC_EP_SETUP); | 
|  | } | 
|  |  | 
|  | if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT0) != 0U) { | 
|  | /* Out (to device) data received */ | 
|  | data->ep_cb[0][ep](ep, USB_DC_EP_DATA_OUT); | 
|  | } | 
|  |  | 
|  | if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT1) != 0U) { | 
|  | /* In (to host) transmit complete */ | 
|  | data->ep_cb[1][ep](ep | USB_SAM0_IN_EP, USB_DC_EP_DATA_IN); | 
|  |  | 
|  | if (data->addr != 0U) { | 
|  | /* Commit the pending address update.  This | 
|  | * must be done after the ack to the host | 
|  | * completes else the ack will get dropped. | 
|  | */ | 
|  | regs->DADD.reg = data->addr; | 
|  | data->addr = 0U; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Top level interrupt handler */ | 
|  | static void usb_sam0_isr(void) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint32_t intflag = regs->INTFLAG.reg; | 
|  | uint32_t epint = regs->EPINTSMRY.reg; | 
|  | uint8_t ep; | 
|  |  | 
|  | /* Acknowledge all interrupts */ | 
|  | regs->INTFLAG.reg = intflag; | 
|  |  | 
|  | if ((intflag & USB_DEVICE_INTFLAG_EORST) != 0U) { | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[0]; | 
|  |  | 
|  | /* The device clears some of the configuration of EP0 | 
|  | * when it receives the EORST.  Re-enable interrupts. | 
|  | */ | 
|  | endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | | 
|  | USB_DEVICE_EPINTENSET_TRCPT1 | | 
|  | USB_DEVICE_EPINTENSET_RXSTP; | 
|  |  | 
|  | data->cb(USB_DC_RESET, NULL); | 
|  | } | 
|  |  | 
|  | /* Dispatch the endpoint interrupts */ | 
|  | for (ep = 0U; epint != 0U; epint >>= 1) { | 
|  | /* Scan bit-by-bit as the Cortex-M0 doesn't have ffs */ | 
|  | if ((epint & 1) != 0U) { | 
|  | usb_sam0_ep_isr(ep); | 
|  | } | 
|  | ep++; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Wait for the device to process the last config write */ | 
|  | static void usb_sam0_wait_syncbusy(void) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  |  | 
|  | while (regs->SYNCBUSY.reg != 0) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Load the pad calibration from the built-in fuses */ | 
|  | static void usb_sam0_load_padcal(void) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint32_t pad_transn; | 
|  | uint32_t pad_transp; | 
|  | uint32_t pad_trim; | 
|  |  | 
|  | #ifdef USB_FUSES_TRANSN_ADDR | 
|  | pad_transn = *(uint32_t *)USB_FUSES_TRANSN_ADDR; | 
|  | #else | 
|  | pad_transn = (*((uint32_t *)(NVMCTRL_OTP4) + | 
|  | (NVM_USB_PAD_TRANSN_POS / 32)) >> | 
|  | (NVM_USB_PAD_TRANSN_POS % 32)) & | 
|  | ((1 << NVM_USB_PAD_TRANSN_SIZE) - 1); | 
|  |  | 
|  | if (pad_transn == 0x1F) { | 
|  | pad_transn = 5U; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | regs->PADCAL.bit.TRANSN = pad_transn; | 
|  |  | 
|  | #ifdef USB_FUSES_TRANSP_ADDR | 
|  | pad_transp = *(uint32_t *)USB_FUSES_TRANSP_ADDR; | 
|  | #else | 
|  | pad_transp = (*((uint32_t *)(NVMCTRL_OTP4) + | 
|  | (NVM_USB_PAD_TRANSP_POS / 32)) >> | 
|  | (NVM_USB_PAD_TRANSP_POS % 32)) & | 
|  | ((1 << NVM_USB_PAD_TRANSP_SIZE) - 1); | 
|  |  | 
|  | if (pad_transp == 0x1F) { | 
|  | pad_transp = 29U; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | regs->PADCAL.bit.TRANSP = pad_transp; | 
|  |  | 
|  | #ifdef USB_FUSES_TRIM_ADDR | 
|  | pad_trim = *(uint32_t *)USB_FUSES_TRIM_ADDR; | 
|  | #else | 
|  | pad_trim = (*((uint32_t *)(NVMCTRL_OTP4) + | 
|  | (NVM_USB_PAD_TRIM_POS / 32)) >> | 
|  | (NVM_USB_PAD_TRIM_POS % 32)) & | 
|  | ((1 << NVM_USB_PAD_TRIM_SIZE) - 1); | 
|  |  | 
|  | if (pad_trim == 0x7) { | 
|  | pad_trim = 3U; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | regs->PADCAL.bit.TRIM = pad_trim; | 
|  | } | 
|  |  | 
|  | #define SAM0_USB_IRQ_CONNECT(n) 				\ | 
|  | do {							\ | 
|  | IRQ_CONNECT(DT_INST_IRQ_BY_IDX(0, n, irq),		\ | 
|  | DT_INST_IRQ_BY_IDX(0, n, priority),		\ | 
|  | usb_sam0_isr, 0, 0);			\ | 
|  | irq_enable(DT_INST_IRQ_BY_IDX(0, n, irq));		\ | 
|  | } while (0) | 
|  |  | 
|  | /* Attach by initializing the device */ | 
|  | int usb_dc_attach(void) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  |  | 
|  | #ifdef MCLK | 
|  | /* Enable the clock in MCLK */ | 
|  | MCLK->APBBMASK.bit.USB_ = 1; | 
|  |  | 
|  | /* Enable the GCLK - use 48 MHz source */ | 
|  | GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_GEN(2) | 
|  | | GCLK_PCHCTRL_CHEN; | 
|  |  | 
|  | while (GCLK->SYNCBUSY.reg) { | 
|  | } | 
|  | #else | 
|  | /* Enable the clock in PM */ | 
|  | PM->APBBMASK.bit.USB_ = 1; | 
|  |  | 
|  | /* Enable the GCLK */ | 
|  | GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_USB | GCLK_CLKCTRL_GEN_GCLK0 | | 
|  | GCLK_CLKCTRL_CLKEN; | 
|  |  | 
|  | while (GCLK->STATUS.bit.SYNCBUSY) { | 
|  | } | 
|  | #endif /* !MCLK */ | 
|  |  | 
|  | /* Configure */ | 
|  | regs->CTRLA.bit.SWRST = 1; | 
|  | usb_sam0_wait_syncbusy(); | 
|  |  | 
|  | /* Change QOS values to have the best performance and correct USB | 
|  | * behaviour | 
|  | */ | 
|  | regs->QOSCTRL.bit.CQOS = 2; | 
|  | regs->QOSCTRL.bit.DQOS = 2; | 
|  |  | 
|  | usb_sam0_load_padcal(); | 
|  |  | 
|  | regs->CTRLA.reg = USB_CTRLA_MODE_DEVICE | USB_CTRLA_RUNSTDBY; | 
|  | regs->CTRLB.reg = USB_DEVICE_CTRLB_SPDCONF_HS; | 
|  |  | 
|  | (void)memset(data->descriptors, 0, sizeof(data->descriptors)); | 
|  | regs->DESCADD.reg = (uintptr_t)&data->descriptors[0]; | 
|  |  | 
|  | regs->INTENSET.reg = USB_DEVICE_INTENSET_EORST; | 
|  |  | 
|  | /* Connect and enable the interrupt */ | 
|  | #if DT_INST_IRQ_HAS_CELL(0, irq) | 
|  | SAM0_USB_IRQ_CONNECT(0); | 
|  | #endif | 
|  | #if DT_INST_IRQ_HAS_IDX(0, 1) | 
|  | SAM0_USB_IRQ_CONNECT(1); | 
|  | #endif | 
|  | #if DT_INST_IRQ_HAS_IDX(0, 2) | 
|  | SAM0_USB_IRQ_CONNECT(2); | 
|  | #endif | 
|  | #if DT_INST_IRQ_HAS_IDX(0, 3) | 
|  | SAM0_USB_IRQ_CONNECT(3); | 
|  | #endif | 
|  |  | 
|  | /* Enable and attach */ | 
|  | regs->CTRLA.bit.ENABLE = 1; | 
|  | usb_sam0_wait_syncbusy(); | 
|  | regs->CTRLB.bit.DETACH = 0; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Detach from the bus */ | 
|  | int usb_dc_detach(void) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  |  | 
|  | regs->CTRLB.bit.DETACH = 1; | 
|  | usb_sam0_wait_syncbusy(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Remove the interrupt and reset the device */ | 
|  | int usb_dc_reset(void) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  |  | 
|  | irq_disable(DT_INST_IRQN(0)); | 
|  |  | 
|  | regs->CTRLA.bit.SWRST = 1; | 
|  | usb_sam0_wait_syncbusy(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Queue a change in address.  This is processed later when the | 
|  | * current transfers are compelete. | 
|  | */ | 
|  | int usb_dc_set_address(const uint8_t addr) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  |  | 
|  | data->addr = addr | USB_DEVICE_DADD_ADDEN; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void usb_dc_set_status_callback(const usb_dc_status_callback cb) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  |  | 
|  | data->cb = cb; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) | 
|  | { | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); | 
|  |  | 
|  | if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { | 
|  | LOG_ERR("invalid endpoint configuration"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (ep_idx > USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address too high"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const cfg) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  | UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; | 
|  | UsbDeviceDescBank *bank; | 
|  | void *buf; | 
|  | int type; | 
|  | int size = -1; | 
|  | int i; | 
|  |  | 
|  | /* Map the type to native type */ | 
|  | switch (cfg->ep_type) { | 
|  | case USB_DC_EP_CONTROL: | 
|  | type = 1; | 
|  | break; | 
|  | case USB_DC_EP_ISOCHRONOUS: | 
|  | type = 2; | 
|  | break; | 
|  | case USB_DC_EP_BULK: | 
|  | type = 3; | 
|  | break; | 
|  | case USB_DC_EP_INTERRUPT: | 
|  | type = 4; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Map the endpoint size to native size */ | 
|  | for (i = 0; i < ARRAY_SIZE(usb_sam0_pcksize_bytes); i++) { | 
|  | if (usb_sam0_pcksize_bytes[i] == cfg->ep_mps) { | 
|  | size = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (size < 0) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (USB_EP_DIR_IS_IN(cfg->ep_addr)) { | 
|  | bank = &desc->DeviceDescBank[1]; | 
|  | } else { | 
|  | bank = &desc->DeviceDescBank[0]; | 
|  | } | 
|  |  | 
|  | buf = (void *)bank->ADDR.reg; | 
|  |  | 
|  | if (bank->PCKSIZE.bit.SIZE != size || buf == NULL) { | 
|  | /* Release the previous buffer, if any */ | 
|  | k_free(buf); | 
|  |  | 
|  | buf = k_malloc(cfg->ep_mps); | 
|  | if (buf == NULL) { | 
|  | return -ENOMEM; | 
|  | } | 
|  | bank->PCKSIZE.bit.SIZE = size; | 
|  | bank->ADDR.reg = (uintptr_t)buf; | 
|  | } | 
|  |  | 
|  | if (USB_EP_DIR_IS_IN(cfg->ep_addr)) { | 
|  | endpoint->EPCFG.bit.EPTYPE1 = type; | 
|  | endpoint->EPSTATUSCLR.bit.BK1RDY = 1; | 
|  | } else { | 
|  | endpoint->EPCFG.bit.EPTYPE0 = type; | 
|  | endpoint->EPSTATUSCLR.bit.BK0RDY = 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_set_stall(const uint8_t ep) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t for_in = USB_EP_GET_DIR(ep); | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (for_in) { | 
|  | endpoint->EPSTATUSSET.bit.STALLRQ1 = 1; | 
|  | } else { | 
|  | endpoint->EPSTATUSSET.bit.STALLRQ0 = 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_clear_stall(const uint8_t ep) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t for_in = USB_EP_GET_DIR(ep); | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (for_in) { | 
|  | endpoint->EPSTATUSCLR.bit.STALLRQ1 = 1; | 
|  | } else { | 
|  | endpoint->EPSTATUSCLR.bit.STALLRQ0 = 1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *stalled) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t for_in = USB_EP_GET_DIR(ep); | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (stalled == NULL) { | 
|  | LOG_ERR("parameter must not be NULL"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (for_in) { | 
|  | *stalled = endpoint->EPSTATUS.bit.STALLRQ1; | 
|  | } else { | 
|  | *stalled = endpoint->EPSTATUS.bit.STALLRQ0; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Halt the selected endpoint */ | 
|  | int usb_dc_ep_halt(uint8_t ep) | 
|  | { | 
|  | return usb_dc_ep_set_stall(ep); | 
|  | } | 
|  |  | 
|  | /* Flush the selected endpoint */ | 
|  | int usb_dc_ep_flush(uint8_t ep) | 
|  | { | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* TODO */ | 
|  | LOG_WRN("flush not implemented"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Enable an endpoint and the endpoint interrupts */ | 
|  | int usb_dc_ep_enable(const uint8_t ep) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t for_in = USB_EP_GET_DIR(ep); | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (for_in) { | 
|  | endpoint->EPSTATUSCLR.bit.BK1RDY = 1; | 
|  | } else { | 
|  | endpoint->EPSTATUSCLR.bit.BK0RDY = 1; | 
|  | } | 
|  |  | 
|  | endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | | 
|  | USB_DEVICE_EPINTENSET_TRCPT1 | | 
|  | USB_DEVICE_EPINTENSET_RXSTP; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Disable the selected endpoint */ | 
|  | int usb_dc_ep_disable(uint8_t ep) | 
|  | { | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | endpoint->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0 | 
|  | | USB_DEVICE_EPINTENCLR_TRCPT1 | 
|  | | USB_DEVICE_EPINTENCLR_RXSTP; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Write a single payload to the IN buffer on the endpoint */ | 
|  | int usb_dc_ep_write(uint8_t ep, const uint8_t *buf, uint32_t len, uint32_t *ret_bytes) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  | UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; | 
|  | uint32_t addr = desc->DeviceDescBank[1].ADDR.reg; | 
|  | uint32_t capacity = usb_sam0_pcksize_bytes[ | 
|  | desc->DeviceDescBank[1].PCKSIZE.bit.SIZE]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (endpoint->EPSTATUS.bit.BK1RDY) { | 
|  | /* Write in progress, drop */ | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | len = Z_MIN(len, capacity); | 
|  |  | 
|  | /* Note that this code does not use the hardware's | 
|  | * multi-packet and automatic zero-length packet features as | 
|  | * the upper layers in Zephyr implement these in code. | 
|  | */ | 
|  | memcpy((void *)addr, buf, len); | 
|  | desc->DeviceDescBank[1].PCKSIZE.bit.MULTI_PACKET_SIZE = 0; | 
|  | desc->DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT = len; | 
|  | endpoint->EPINTFLAG.reg = | 
|  | USB_DEVICE_EPINTFLAG_TRCPT1 | USB_DEVICE_EPINTFLAG_TRFAIL1; | 
|  | endpoint->EPSTATUSSET.bit.BK1RDY = 1; | 
|  |  | 
|  | if (ret_bytes != NULL) { | 
|  | *ret_bytes = len; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Read data from an OUT endpoint */ | 
|  | int usb_dc_ep_read_ex(uint8_t ep, uint8_t *buf, uint32_t max_data_len, | 
|  | uint32_t *read_bytes, bool wait) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  | UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; | 
|  | uint32_t addr = desc->DeviceDescBank[0].ADDR.reg; | 
|  | uint32_t bytes = desc->DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT; | 
|  | uint32_t take; | 
|  | int remain; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (!endpoint->EPSTATUS.bit.BK0RDY) { | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | /* The code below emulates the Quark FIFO which the Zephyr USB | 
|  | * API is based on.  Reading with buf == NULL returns the | 
|  | * number of bytes available and starts the read.  The caller | 
|  | * then keeps calling until all bytes are consumed which | 
|  | * also marks the OUT buffer as freed. | 
|  | */ | 
|  | if (buf == NULL) { | 
|  | data->out_at = 0U; | 
|  |  | 
|  | if (read_bytes != NULL) { | 
|  | *read_bytes = bytes; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | remain = bytes - data->out_at; | 
|  | take = MIN(max_data_len, remain); | 
|  | memcpy(buf, (uint8_t *)addr + data->out_at, take); | 
|  |  | 
|  | if (read_bytes != NULL) { | 
|  | *read_bytes = take; | 
|  | } | 
|  |  | 
|  | if (take == remain) { | 
|  | if (!wait) { | 
|  | endpoint->EPSTATUSCLR.bit.BK0RDY = 1; | 
|  | data->out_at = 0U; | 
|  | } | 
|  | } else { | 
|  | data->out_at += take; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_read(uint8_t ep, uint8_t *buf, uint32_t max_data_len, uint32_t *read_bytes) | 
|  | { | 
|  | return usb_dc_ep_read_ex(ep, buf, max_data_len, read_bytes, false); | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_read_wait(uint8_t ep, uint8_t *buf, uint32_t max_data_len, | 
|  | uint32_t *read_bytes) | 
|  | { | 
|  | return usb_dc_ep_read_ex(ep, buf, max_data_len, read_bytes, true); | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_read_continue(uint8_t ep) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | endpoint->EPSTATUSCLR.bit.BK0RDY = 1; | 
|  | data->out_at = 0U; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | uint8_t for_in = USB_EP_GET_DIR(ep); | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | data->ep_cb[for_in ? 1 : 0][ep_idx] = cb; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int usb_dc_ep_mps(const uint8_t ep) | 
|  | { | 
|  | struct usb_sam0_data *data = usb_sam0_get_data(); | 
|  | UsbDevice *regs = ®S->DEVICE; | 
|  | uint8_t for_in = USB_EP_GET_DIR(ep); | 
|  | uint8_t ep_idx = USB_EP_GET_IDX(ep); | 
|  | UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; | 
|  | UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; | 
|  |  | 
|  | if (ep_idx >= USB_NUM_ENDPOINTS) { | 
|  | LOG_ERR("endpoint index/address out of range"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (for_in) { | 
|  | /* if endpoint is not configured, this should return 0 */ | 
|  | if (endpoint->EPCFG.bit.EPTYPE1 == 0) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return usb_sam0_pcksize_bytes[ | 
|  | desc->DeviceDescBank[1].PCKSIZE.bit.SIZE]; | 
|  | } else { | 
|  | /* if endpoint is not configured, this should return 0 */ | 
|  | if (endpoint->EPCFG.bit.EPTYPE0 == 0) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return usb_sam0_pcksize_bytes[ | 
|  | desc->DeviceDescBank[0].PCKSIZE.bit.SIZE]; | 
|  | } | 
|  | } |