/* * 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 LOG_MODULE_REGISTER(usb_dc_sam0); #include #include #include #include #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; PINCTRL_DT_INST_DEFINE(0); static const struct pinctrl_dev_config *pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(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 (false) /* Attach by initializing the device */ int usb_dc_attach(void) { UsbDevice *regs = ®S->DEVICE; struct usb_sam0_data *data = usb_sam0_get_data(); int retval; #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; retval = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); if (retval < 0) { return retval; } 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 complete. */ 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]; } }