| /* |
| * Copyright (c) 2024 Renesas Electronics Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* |
| * Driver for the Smartbond USB device controller. |
| */ |
| |
| #include <string.h> |
| |
| #include "udc_common.h" |
| |
| #include <DA1469xAB.h> |
| #include <da1469x_config.h> |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/drivers/usb/udc.h> |
| #include <zephyr/drivers/usb/usb_dc.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/smartbond_clock_control.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/pm/policy.h> |
| |
| LOG_MODULE_REGISTER(udc_smartbond, CONFIG_UDC_DRIVER_LOG_LEVEL); |
| |
| /* Size of hardware RX and TX FIFO. */ |
| #define EP0_FIFO_SIZE 8 |
| #define EP_FIFO_SIZE 64 |
| |
| /* |
| * DA146xx register fields and bit mask are very long. Filed masks repeat register names. |
| * Those convenience macros are a way to reduce complexity of register modification lines. |
| */ |
| #define GET_BIT(val, field) (val & field##_Msk) >> field##_Pos |
| #define REG_GET_BIT(reg, field) (USB->reg & USB_##reg##_##field##_Msk) |
| #define REG_SET_BIT(reg, field) (USB->reg |= USB_##reg##_##field##_Msk) |
| #define REG_CLR_BIT(reg, field) (USB->reg &= ~USB_##reg##_##field##_Msk) |
| #define REG_SET_VAL(reg, field, val) \ |
| (USB->reg = (USB->reg & ~USB_##reg##_##field##_Msk) | (val << USB_##reg##_##field##_Pos)) |
| |
| struct usb_smartbond_dma_config { |
| int tx_chan; |
| int rx_chan; |
| uint8_t tx_slot_mux; |
| uint8_t rx_slot_mux; |
| const struct device *tx_dev; |
| const struct device *rx_dev; |
| }; |
| |
| struct udc_smartbond_config { |
| IRQn_Type udc_irq; |
| IRQn_Type vbus_irq; |
| uint8_t fifo_read_threshold; |
| uint8_t num_of_eps; |
| uint16_t dma_min_transfer_size; |
| struct usb_smartbond_dma_config dma_cfg; |
| }; |
| |
| /* Node functional states */ |
| #define NFSR_NODE_RESET 0 |
| #define NFSR_NODE_RESUME 1 |
| #define NFSR_NODE_OPERATIONAL 2 |
| #define NFSR_NODE_SUSPEND 3 |
| /* |
| * Those two following states are added to allow going out of sleep mode |
| * using frame interrupt. On remove wakeup RESUME state must be kept for |
| * at least 1ms. It is accomplished by using FRAME interrupt that goes |
| * through those two fake states before entering OPERATIONAL state. |
| */ |
| #define NFSR_NODE_WAKING (0x10 | (NFSR_NODE_RESUME)) |
| #define NFSR_NODE_WAKING2 (0x20 | (NFSR_NODE_RESUME)) |
| |
| struct smartbond_ep_reg_set { |
| volatile uint32_t epc_in; |
| volatile uint32_t txd; |
| volatile uint32_t txs; |
| volatile uint32_t txc; |
| volatile uint32_t epc_out; |
| volatile uint32_t rxd; |
| volatile uint32_t rxs; |
| volatile uint32_t rxc; |
| }; |
| |
| struct smartbond_ep_state { |
| struct udc_ep_config config; |
| struct smartbond_ep_reg_set *regs; |
| struct net_buf *buf; |
| /** Packet size sent or received so far. It is used to modify transferred field |
| * after ACK is received or when filling ISO endpoint with size larger then |
| * FIFO size. |
| */ |
| uint16_t last_packet_size; |
| uint8_t iso: 1; /** ISO endpoint */ |
| }; |
| |
| struct usb_smartbond_dma_data { |
| struct dma_config tx_cfg; |
| struct dma_config rx_cfg; |
| struct dma_block_config tx_block_cfg; |
| struct dma_block_config rx_block_cfg; |
| }; |
| |
| struct usb_smartbond_data { |
| struct udc_data udc_data; |
| struct usb_smartbond_dma_data dma_data; |
| const struct device *dev; |
| struct k_work ep0_setup_work; |
| struct k_work ep0_tx_work; |
| struct k_work ep0_rx_work; |
| uint8_t setup_buffer[8]; |
| bool vbus_present; |
| bool attached; |
| atomic_t clk_requested; |
| uint8_t nfsr; |
| struct smartbond_ep_state ep_state[2][4]; |
| atomic_ptr_t dma_ep[2]; /** DMA used by channel */ |
| }; |
| |
| #define EP0_OUT_STATE(data) (&data->ep_state[0][0]) |
| #define EP0_IN_STATE(data) (&data->ep_state[1][0]) |
| |
| static int usb_smartbond_dma_config(struct usb_smartbond_data *data) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| const struct usb_smartbond_dma_config *dma_cfg = &config->dma_cfg; |
| struct dma_config *tx = &data->dma_data.tx_cfg; |
| struct dma_config *rx = &data->dma_data.rx_cfg; |
| struct dma_block_config *tx_block = &data->dma_data.tx_block_cfg; |
| struct dma_block_config *rx_block = &data->dma_data.rx_block_cfg; |
| |
| if (dma_request_channel(dma_cfg->rx_dev, (void *)&dma_cfg->rx_chan) < 0) { |
| LOG_ERR("RX DMA channel is already occupied"); |
| return -EIO; |
| } |
| |
| if (dma_request_channel(dma_cfg->tx_dev, (void *)&dma_cfg->tx_chan) < 0) { |
| LOG_ERR("TX DMA channel is already occupied"); |
| return -EIO; |
| } |
| |
| tx->channel_direction = MEMORY_TO_PERIPHERAL; |
| tx->dma_callback = NULL; |
| tx->user_data = NULL; |
| tx->block_count = 1; |
| tx->head_block = tx_block; |
| |
| tx->error_callback_dis = 1; |
| /* DMA callback is not used */ |
| tx->complete_callback_en = 1; |
| |
| tx->dma_slot = dma_cfg->tx_slot_mux; |
| tx->channel_priority = 7; |
| |
| /* Burst mode is not using when DREQ is one */ |
| tx->source_burst_length = 1; |
| tx->dest_burst_length = 1; |
| /* USB is byte-oriented protocol */ |
| tx->source_data_size = 1; |
| tx->dest_data_size = 1; |
| |
| /* Do not change */ |
| tx_block->dest_addr_adj = 0x2; |
| /* Incremental */ |
| tx_block->source_addr_adj = 0x0; |
| |
| /* Should reflect TX buffer */ |
| tx_block->source_address = 0; |
| /* Should reflect USB TX FIFO. Temporarily assign an SRAM location. */ |
| tx_block->dest_address = MCU_SYSRAM_M_BASE; |
| /* Should reflect total bytes to be transmitted */ |
| tx_block->block_size = 0; |
| |
| rx->channel_direction = PERIPHERAL_TO_MEMORY; |
| rx->dma_callback = NULL; |
| rx->user_data = NULL; |
| rx->block_count = 1; |
| rx->head_block = rx_block; |
| |
| rx->error_callback_dis = 1; |
| /* DMA callback is not used */ |
| rx->complete_callback_en = 1; |
| |
| rx->dma_slot = dma_cfg->rx_slot_mux; |
| rx->channel_priority = 2; |
| |
| /* Burst mode is not using when DREQ is one */ |
| rx->source_burst_length = 1; |
| rx->dest_burst_length = 1; |
| /* USB is byte-oriented protocol */ |
| rx->source_data_size = 1; |
| rx->dest_data_size = 1; |
| |
| /* Do not change */ |
| rx_block->source_addr_adj = 0x2; |
| /* Incremental */ |
| rx_block->dest_addr_adj = 0x0; |
| |
| /* Should reflect USB RX FIFO */ |
| rx_block->source_address = 0; |
| /* Should reflect RX buffer. Temporarily assign an SRAM location. */ |
| rx_block->dest_address = MCU_SYSRAM_M_BASE; |
| /* Should reflect total bytes to be received */ |
| rx_block->block_size = 0; |
| |
| if (dma_config(dma_cfg->rx_dev, dma_cfg->rx_chan, rx) < 0) { |
| LOG_ERR("RX DMA configuration failed"); |
| return -EINVAL; |
| } |
| |
| if (dma_config(dma_cfg->tx_dev, dma_cfg->tx_chan, tx) < 0) { |
| LOG_ERR("TX DMA configuration failed"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void usb_smartbond_dma_deconfig(struct usb_smartbond_data *data) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| const struct usb_smartbond_dma_config *dma_cfg = &config->dma_cfg; |
| |
| dma_stop(dma_cfg->tx_dev, dma_cfg->tx_chan); |
| dma_stop(dma_cfg->rx_dev, dma_cfg->rx_chan); |
| |
| dma_release_channel(dma_cfg->tx_dev, dma_cfg->tx_chan); |
| dma_release_channel(dma_cfg->rx_dev, dma_cfg->rx_chan); |
| } |
| |
| static struct smartbond_ep_state *usb_dc_get_ep_state(struct usb_smartbond_data *data, uint8_t ep) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| uint8_t ep_dir = USB_EP_GET_DIR(ep) ? 1 : 0; |
| |
| return (ep_idx < config->num_of_eps) ? &data->ep_state[ep_dir][ep_idx] : NULL; |
| } |
| |
| static struct smartbond_ep_state *usb_dc_get_ep_out_state(struct usb_smartbond_data *data, |
| uint8_t ep_idx) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| |
| return ep_idx < config->num_of_eps ? &data->ep_state[0][ep_idx] : NULL; |
| } |
| |
| static struct smartbond_ep_state *usb_dc_get_ep_in_state(struct usb_smartbond_data *data, |
| uint8_t ep_idx) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| |
| return ep_idx < config->num_of_eps ? &data->ep_state[1][ep_idx] : NULL; |
| } |
| |
| static void set_nfsr(struct usb_smartbond_data *data, uint8_t val) |
| { |
| data->nfsr = val; |
| /* |
| * Write only lower 2 bits to register, higher bits are used |
| * to count down till OPERATIONAL state can be entered when |
| * remote wakeup activated. |
| */ |
| USB->USB_NFSR_REG = val & 3; |
| } |
| |
| static void fill_tx_fifo(struct smartbond_ep_state *ep_state) |
| { |
| int remaining; |
| const uint8_t *src; |
| struct smartbond_ep_reg_set *regs = ep_state->regs; |
| struct net_buf *buf = ep_state->buf; |
| const struct udc_ep_config *const ep_cfg = &ep_state->config; |
| const uint16_t mps = udc_mps_ep_size(ep_cfg); |
| const uint8_t ep_idx = USB_EP_GET_IDX(ep_cfg->addr); |
| |
| src = buf->data; |
| remaining = buf->len; |
| if (remaining > mps - ep_state->last_packet_size) { |
| remaining = mps - ep_state->last_packet_size; |
| } |
| |
| /* |
| * Loop checks TCOUNT all the time since this value is saturated to 31 |
| * and can't be read just once before. |
| */ |
| while ((regs->txs & USB_USB_TXS1_REG_USB_TCOUNT_Msk) > 0 && remaining > 0) { |
| regs->txd = *src++; |
| ep_state->last_packet_size++; |
| remaining--; |
| } |
| |
| /* |
| * Setup FIFO level warning in case whole packet could not be placed |
| * in FIFO at once. This case only applies to ISO endpoints with packet |
| * size grater then 64. All other packets will fit in corresponding |
| * FIFO and there is no need for enabling FIFO level interrupt. |
| */ |
| if (ep_idx == 0 || ep_cfg->mps <= EP_FIFO_SIZE) { |
| return; |
| } |
| |
| if (remaining > 0) { |
| /* |
| * Max packet size is set to value greater then FIFO. |
| * Enable fifo level warning to handle larger packets. |
| */ |
| regs->txc |= (3 << USB_USB_TXC1_REG_USB_TFWL_Pos); |
| USB->USB_FWMSK_REG |= BIT(ep_idx - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos); |
| } else { |
| regs->txc &= ~USB_USB_TXC1_REG_USB_TFWL_Msk; |
| USB->USB_FWMSK_REG &= ~(BIT(ep_idx - 1 + USB_USB_FWMSK_REG_USB_M_TXWARN31_Pos)); |
| /* Whole packet already in fifo, no need to |
| * refill it later. Mark last. |
| */ |
| regs->txc |= USB_USB_TXC1_REG_USB_LAST_Msk; |
| } |
| } |
| |
| static bool try_allocate_dma(struct usb_smartbond_data *data, struct smartbond_ep_state *ep_state) |
| { |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| const uint8_t ep = ep_cfg->addr; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| uint8_t dir_ix = USB_EP_DIR_IS_OUT(ep) ? 0 : 1; |
| |
| if (atomic_ptr_cas(&data->dma_ep[dir_ix], NULL, ep_state)) { |
| if (dir_ix == 0) { |
| USB->USB_DMA_CTRL_REG = |
| (USB->USB_DMA_CTRL_REG & ~USB_USB_DMA_CTRL_REG_USB_DMA_RX_Msk) | |
| ((ep_idx - 1) << USB_USB_DMA_CTRL_REG_USB_DMA_RX_Pos); |
| } else { |
| USB->USB_DMA_CTRL_REG = |
| (USB->USB_DMA_CTRL_REG & ~USB_USB_DMA_CTRL_REG_USB_DMA_TX_Msk) | |
| ((ep_idx - 1) << USB_USB_DMA_CTRL_REG_USB_DMA_TX_Pos); |
| } |
| USB->USB_DMA_CTRL_REG |= USB_USB_DMA_CTRL_REG_USB_DMA_EN_Msk; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static void start_rx_dma(const struct usb_smartbond_dma_config *dma_cfg, uintptr_t src, |
| uintptr_t dst, uint16_t size) |
| { |
| if (dma_reload(dma_cfg->rx_dev, dma_cfg->rx_chan, src, dst, size) < 0) { |
| LOG_ERR("Failed to reload RX DMA"); |
| } else { |
| dma_start(dma_cfg->rx_dev, dma_cfg->rx_chan); |
| } |
| } |
| |
| static void start_rx_packet(struct usb_smartbond_data *data, struct smartbond_ep_state *ep_state) |
| { |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| const struct udc_smartbond_config *config = data->dev->config; |
| const uint8_t ep = ep_cfg->addr; |
| struct smartbond_ep_reg_set *regs = ep_state->regs; |
| struct net_buf *buf = ep_state->buf; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| const uint16_t mps = udc_mps_ep_size(ep_cfg); |
| uint8_t rxc = regs->rxc | USB_USB_RXC1_REG_USB_RX_EN_Msk; |
| |
| LOG_DBG("Start rx ep 0x%02x", ep); |
| |
| ep_state->last_packet_size = 0; |
| |
| if (mps > config->dma_min_transfer_size) { |
| if (try_allocate_dma(data, ep_state)) { |
| start_rx_dma(&config->dma_cfg, (uintptr_t)®s->rxd, |
| (uintptr_t)net_buf_tail(buf), mps); |
| } else if (mps > EP_FIFO_SIZE) { |
| /* |
| * Other endpoint is using DMA in that direction, |
| * fall back to interrupts. |
| * For endpoint size greater than FIFO size, |
| * enable FIFO level warning interrupt when FIFO |
| * has less than 17 bytes free. |
| */ |
| rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk; |
| USB->USB_FWMSK_REG |= |
| BIT(ep_idx - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos); |
| } |
| } else if (ep_idx != 0) { |
| /* If max_packet_size would fit in FIFO no need |
| * for FIFO level warning interrupt. |
| */ |
| rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk; |
| USB->USB_FWMSK_REG &= ~(BIT(ep_idx - 1 + USB_USB_FWMSK_REG_USB_M_RXWARN31_Pos)); |
| } |
| |
| regs->rxc = rxc; |
| } |
| |
| static void start_tx_dma(const struct usb_smartbond_dma_config *dma_cfg, uintptr_t src, |
| uintptr_t dst, uint16_t size) |
| { |
| if (dma_reload(dma_cfg->tx_dev, dma_cfg->tx_chan, src, dst, size) < 0) { |
| LOG_ERR("Failed to reload TX DMA"); |
| } else { |
| dma_start(dma_cfg->tx_dev, dma_cfg->tx_chan); |
| } |
| } |
| |
| static void start_tx_packet(struct usb_smartbond_data *data, struct smartbond_ep_state *ep_state) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| struct smartbond_ep_reg_set *regs = ep_state->regs; |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| struct net_buf *buf = ep_state->buf; |
| const uint8_t ep = ep_cfg->addr; |
| uint16_t remaining = buf->len; |
| const uint16_t mps = udc_mps_ep_size(ep_cfg); |
| uint16_t size = MIN(remaining, mps); |
| uint8_t txc; |
| |
| LOG_DBG("ep 0x%02x %d/%d", ep, size, remaining); |
| |
| ep_state->last_packet_size = 0; |
| |
| regs->txc = USB_USB_TXC1_REG_USB_FLUSH_Msk; |
| |
| txc = USB_USB_TXC1_REG_USB_TX_EN_Msk | USB_USB_TXC1_REG_USB_LAST_Msk; |
| if (ep_cfg->stat.data1) { |
| txc |= USB_USB_TXC1_REG_USB_TOGGLE_TX_Msk; |
| } |
| |
| if (ep != USB_CONTROL_EP_IN && size > config->dma_min_transfer_size && |
| (uint32_t)(buf->data) >= CONFIG_SRAM_BASE_ADDRESS && try_allocate_dma(data, ep_state)) { |
| start_tx_dma(&config->dma_cfg, (uintptr_t)buf->data, (uintptr_t)®s->txd, size); |
| } else { |
| fill_tx_fifo(ep_state); |
| } |
| |
| regs->txc = txc; |
| if (ep == USB_CONTROL_EP_IN) { |
| (void)USB->USB_EP0_NAK_REG; |
| /* |
| * While driver expects upper layer to send data to the host, |
| * code should detect EP0 NAK event that could mean that |
| * host already sent ZLP without waiting for all requested |
| * data. |
| */ |
| REG_SET_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); |
| } |
| } |
| |
| static uint16_t read_rx_fifo(struct smartbond_ep_state *ep_state, uint8_t *dst, |
| uint16_t bytes_in_fifo) |
| { |
| struct smartbond_ep_reg_set *regs = ep_state->regs; |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| const uint16_t mps = udc_mps_ep_size(ep_cfg); |
| uint16_t remaining = mps - ep_state->last_packet_size; |
| uint16_t receive_this_time = bytes_in_fifo; |
| |
| if (remaining < bytes_in_fifo) { |
| receive_this_time = remaining; |
| } |
| |
| for (int i = 0; i < receive_this_time; ++i) { |
| dst[i] = regs->rxd; |
| } |
| |
| ep_state->last_packet_size += receive_this_time; |
| |
| return bytes_in_fifo - receive_this_time; |
| } |
| |
| static void handle_ep0_rx(struct usb_smartbond_data *data) |
| { |
| int fifo_bytes; |
| uint32_t rxs0 = USB->USB_RXS0_REG; |
| struct smartbond_ep_state *ep0_out_state = EP0_OUT_STATE(data); |
| struct udc_ep_config *ep0_out_config = &ep0_out_state->config; |
| struct smartbond_ep_state *ep0_in_state; |
| struct udc_ep_config *ep0_in_config; |
| struct net_buf *buf = ep0_out_state->buf; |
| |
| fifo_bytes = GET_BIT(rxs0, USB_USB_RXS0_REG_USB_RCOUNT); |
| |
| if (rxs0 & USB_USB_RXS0_REG_USB_SETUP_Msk) { |
| ep0_in_state = EP0_IN_STATE(data); |
| ep0_in_config = &ep0_in_state->config; |
| ep0_out_state->last_packet_size = 0; |
| read_rx_fifo(ep0_out_state, data->setup_buffer, EP0_FIFO_SIZE); |
| |
| ep0_out_config->stat.halted = 0; |
| ep0_out_config->stat.data1 = 1; |
| ep0_in_config->stat.halted = 0; |
| ep0_in_config->stat.data1 = 1; |
| REG_SET_BIT(USB_TXC0_REG, USB_TOGGLE_TX0); |
| REG_CLR_BIT(USB_EPC0_REG, USB_STALL); |
| LOG_HEXDUMP_DBG(data->setup_buffer, 8, "setup"); |
| REG_CLR_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); |
| REG_CLR_BIT(USB_RXC0_REG, USB_RX_EN); |
| (void)USB->USB_EP0_NAK_REG; |
| k_work_submit_to_queue(udc_get_work_q(), &data->ep0_setup_work); |
| } else { |
| (void)USB->USB_EP0_NAK_REG; |
| if (GET_BIT(rxs0, USB_USB_RXS0_REG_USB_TOGGLE_RX0) != ep0_out_config->stat.data1) { |
| /* Toggle bit does not match discard packet */ |
| REG_SET_BIT(USB_RXC0_REG, USB_FLUSH); |
| ep0_out_state->last_packet_size = 0; |
| LOG_WRN("Packet with incorrect data1 bit rejected"); |
| } else { |
| read_rx_fifo(ep0_out_state, |
| net_buf_tail(buf) + ep0_out_state->last_packet_size, |
| fifo_bytes); |
| if (rxs0 & USB_USB_RXS0_REG_USB_RX_LAST_Msk) { |
| ep0_out_config->stat.data1 ^= 1; |
| net_buf_add(ep0_out_state->buf, ep0_out_state->last_packet_size); |
| if (ep0_out_state->last_packet_size < EP0_FIFO_SIZE || |
| ep0_out_state->buf->len == 0) { |
| k_work_submit_to_queue(udc_get_work_q(), |
| &data->ep0_rx_work); |
| } else { |
| start_rx_packet(data, ep0_out_state); |
| } |
| } |
| } |
| } |
| } |
| |
| static void udc_smartbond_ep_abort(const struct device *dev, struct udc_ep_config *const ep_cfg) |
| { |
| struct smartbond_ep_state *ep_state = (struct smartbond_ep_state *)(ep_cfg); |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| const struct udc_smartbond_config *config = data->dev->config; |
| |
| /* Stop DMA if it used by this endpoint */ |
| if (data->dma_ep[0] == ep_state) { |
| dma_stop(config->dma_cfg.rx_dev, config->dma_cfg.rx_chan); |
| data->dma_ep[0] = NULL; |
| } else if (data->dma_ep[1] == ep_state) { |
| dma_stop(config->dma_cfg.tx_dev, config->dma_cfg.tx_chan); |
| data->dma_ep[1] = NULL; |
| } |
| /* Flush FIFO */ |
| if (USB_EP_DIR_IS_OUT(ep_cfg->addr)) { |
| ep_state->regs->rxc |= USB_USB_RXC0_REG_USB_FLUSH_Msk; |
| ep_state->regs->rxc &= ~USB_USB_RXC0_REG_USB_FLUSH_Msk; |
| } else { |
| ep_state->regs->txc |= USB_USB_TXC0_REG_USB_FLUSH_Msk; |
| ep_state->regs->txc &= ~USB_USB_TXC0_REG_USB_FLUSH_Msk; |
| } |
| } |
| |
| static int udc_smartbond_ep_tx(const struct device *dev, uint8_t ep) |
| { |
| struct usb_smartbond_data *data = dev->data; |
| struct smartbond_ep_state *ep_state = usb_dc_get_ep_in_state(data, USB_EP_GET_IDX(ep)); |
| struct net_buf *buf; |
| |
| if (udc_ep_is_busy(dev, ep) || |
| (ep_state->regs->epc_in & USB_USB_EPC1_REG_USB_STALL_Msk) != 0) { |
| return 0; |
| } |
| |
| buf = udc_buf_peek(dev, ep); |
| LOG_DBG("TX ep 0x%02x len %u", ep, buf ? buf->len : -1); |
| |
| if (buf) { |
| ep_state->buf = buf; |
| ep_state->last_packet_size = 0; |
| |
| start_tx_packet(data, ep_state); |
| |
| udc_ep_set_busy(dev, ep, true); |
| } |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_ep_rx(const struct device *dev, uint8_t ep) |
| { |
| struct usb_smartbond_data *data = dev->data; |
| struct smartbond_ep_state *ep_state = usb_dc_get_ep_out_state(data, USB_EP_GET_IDX(ep)); |
| struct net_buf *buf; |
| |
| if (udc_ep_is_busy(dev, ep)) { |
| return 0; |
| } |
| |
| buf = udc_buf_peek(dev, ep); |
| |
| if (buf) { |
| LOG_DBG("RX ep 0x%02x len %u", ep, buf->size); |
| |
| ep_state->last_packet_size = 0; |
| ep_state->buf = buf; |
| |
| start_rx_packet(data, ep_state); |
| |
| udc_ep_set_busy(dev, ep, true); |
| } |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_ep_enqueue(const struct device *dev, struct udc_ep_config *const ep_cfg, |
| struct net_buf *buf) |
| { |
| unsigned int lock_key; |
| const uint8_t ep = ep_cfg->addr; |
| int ret; |
| |
| LOG_DBG("ep 0x%02x enqueue %p", ep, buf); |
| udc_buf_put(ep_cfg, buf); |
| |
| if (ep_cfg->stat.halted) { |
| /* |
| * It is fine to enqueue a transfer for a halted endpoint, |
| * you need to make sure that transfers are re-triggered when |
| * the halt is cleared. |
| */ |
| LOG_DBG("ep 0x%02x halted", ep); |
| return 0; |
| } |
| |
| lock_key = irq_lock(); |
| |
| if (USB_EP_DIR_IS_IN(ep)) { |
| ret = udc_smartbond_ep_tx(dev, ep); |
| } else { |
| ret = udc_smartbond_ep_rx(dev, ep); |
| } |
| |
| irq_unlock(lock_key); |
| |
| return ret; |
| } |
| |
| static int udc_smartbond_ep_dequeue(const struct device *dev, struct udc_ep_config *const ep_cfg) |
| { |
| const uint8_t ep = ep_cfg->addr; |
| unsigned int lock_key; |
| struct net_buf *buf; |
| |
| LOG_INF("ep 0x%02x dequeue all", ep); |
| |
| lock_key = irq_lock(); |
| |
| udc_smartbond_ep_abort(dev, ep_cfg); |
| |
| buf = udc_buf_get_all(dev, ep); |
| if (buf) { |
| udc_submit_ep_event(dev, buf, -ECONNABORTED); |
| } |
| |
| udc_ep_set_busy(dev, ep, false); |
| |
| irq_unlock(lock_key); |
| |
| return 0; |
| } |
| |
| int udc_smartbond_ep_enable(const struct device *dev, struct udc_ep_config *const ep_cfg) |
| { |
| const uint8_t ep = ep_cfg->addr; |
| struct smartbond_ep_state *ep_state; |
| bool iso = (ep_cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) == USB_EP_TYPE_ISO; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| ARG_UNUSED(dev); |
| |
| LOG_INF("Enable ep 0x%02x", ep); |
| |
| ep_state = (struct smartbond_ep_state *)(ep_cfg); |
| if (USB_EP_DIR_IS_IN(ep)) { |
| ep_state->regs->txc &= ~USB_USB_TXC0_REG_USB_IGN_IN_Msk; |
| if (ep != USB_CONTROL_EP_IN) { |
| ep_state->regs->epc_in |= USB_USB_EPC1_REG_USB_EP_EN_Msk | |
| USB_EP_GET_IDX(ep) | |
| (iso ? USB_USB_EPC2_REG_USB_ISO_Msk : 0); |
| USB->USB_TXMSK_REG |= 0x11 << (ep_idx - 1); |
| REG_SET_BIT(USB_MAMSK_REG, USB_M_TX_EV); |
| } |
| } else { |
| ep_state->regs->rxc &= ~USB_USB_RXC0_REG_USB_IGN_OUT_Msk; |
| if (ep == USB_CONTROL_EP_OUT) { |
| ep_state->regs->rxc &= ~USB_USB_RXC0_REG_USB_IGN_SETUP_Msk; |
| } else { |
| ep_state->regs->epc_out = USB_USB_EPC2_REG_USB_EP_EN_Msk | |
| USB_EP_GET_IDX(ep) | |
| (iso ? USB_USB_EPC2_REG_USB_ISO_Msk : 0); |
| USB->USB_RXMSK_REG |= 0x11 << (ep_idx - 1); |
| REG_SET_BIT(USB_MAMSK_REG, USB_M_RX_EV); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_ep_disable(const struct device *dev, struct udc_ep_config *const ep_cfg) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| const uint8_t ep = ep_cfg->addr; |
| struct smartbond_ep_state *ep_state; |
| |
| LOG_INF("Disable ep 0x%02x", ep); |
| |
| ep_state = usb_dc_get_ep_state(data, ep); |
| if (USB_EP_DIR_IS_IN(ep)) { |
| ep_state->regs->txc = |
| USB_USB_TXC0_REG_USB_IGN_IN_Msk | USB_USB_TXC0_REG_USB_FLUSH_Msk; |
| } else { |
| ep_state->regs->rxc = |
| USB_USB_RXC0_REG_USB_IGN_SETUP_Msk | USB_USB_RXC0_REG_USB_IGN_OUT_Msk; |
| } |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_ep_set_halt(const struct device *dev, struct udc_ep_config *const ep_cfg) |
| { |
| struct smartbond_ep_state *ep_state = (struct smartbond_ep_state *)(ep_cfg); |
| struct net_buf *buf; |
| const uint8_t ep = ep_cfg->addr; |
| |
| LOG_DBG("Set halt ep 0x%02x", ep); |
| |
| ep_cfg->stat.halted = 1; |
| if (ep_cfg->addr == USB_CONTROL_EP_IN) { |
| /* Stall in DATA IN phase, drop status OUT packet */ |
| if (udc_ctrl_stage_is_data_in(dev)) { |
| buf = udc_buf_get(dev, USB_CONTROL_EP_OUT); |
| if (buf) { |
| net_buf_unref(buf); |
| } |
| } |
| USB->USB_RXC0_REG = USB_USB_RXC0_REG_USB_FLUSH_Msk; |
| USB->USB_EPC0_REG |= USB_USB_EPC0_REG_USB_STALL_Msk; |
| USB->USB_TXC0_REG |= USB_USB_TXC0_REG_USB_TX_EN_Msk; |
| } else if (ep == USB_CONTROL_EP_OUT) { |
| ep_state->regs->rxc |= USB_USB_RXC0_REG_USB_RX_EN_Msk; |
| ep_state->regs->epc_in |= USB_USB_EPC0_REG_USB_STALL_Msk; |
| } else if (USB_EP_DIR_IS_OUT(ep)) { |
| ep_state->regs->epc_out = USB_USB_EPC1_REG_USB_STALL_Msk; |
| ep_state->regs->rxc = USB_USB_RXC1_REG_USB_RX_EN_Msk; |
| } else { |
| ep_state->regs->epc_in |= USB_USB_EPC1_REG_USB_STALL_Msk; |
| ep_state->regs->txc = |
| USB_USB_TXC1_REG_USB_TX_EN_Msk | USB_USB_TXC1_REG_USB_LAST_Msk; |
| } |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_ep_clear_halt(const struct device *dev, struct udc_ep_config *const ep_cfg) |
| { |
| const uint8_t ep = ep_cfg->addr; |
| struct smartbond_ep_state *ep_state = (struct smartbond_ep_state *)(ep_cfg); |
| |
| LOG_DBG("Clear halt ep 0x%02x", ep); |
| |
| ep_cfg->stat.data1 = 0; |
| ep_cfg->stat.halted = 0; |
| |
| if (ep == USB_CONTROL_EP_OUT || ep == USB_CONTROL_EP_IN) { |
| REG_CLR_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); |
| } |
| |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| ep_state->regs->epc_out &= ~USB_USB_EPC1_REG_USB_STALL_Msk; |
| udc_smartbond_ep_rx(dev, ep); |
| } else { |
| ep_state->regs->epc_in &= ~USB_USB_EPC1_REG_USB_STALL_Msk; |
| udc_smartbond_ep_tx(dev, ep); |
| } |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_set_address(const struct device *dev, const uint8_t addr) |
| { |
| ARG_UNUSED(dev); |
| |
| LOG_DBG("Set new address %u for %p", addr, dev); |
| |
| USB->USB_FAR_REG = (addr & USB_USB_FAR_REG_USB_AD_Msk) | USB_USB_FAR_REG_USB_AD_EN_Msk; |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_host_wakeup(const struct device *dev) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| |
| LOG_DBG("Remote wakeup from %p", dev); |
| |
| if (data->nfsr == NFSR_NODE_SUSPEND) { |
| /* |
| * Enter fake state that will use FRAME interrupt to wait before |
| * going operational. |
| */ |
| set_nfsr(data, NFSR_NODE_WAKING); |
| USB->USB_MAMSK_REG |= USB_USB_MAMSK_REG_USB_M_FRAME_Msk; |
| } |
| |
| return 0; |
| } |
| |
| static enum udc_bus_speed udc_smartbond_device_speed(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| return UDC_BUS_SPEED_FS; |
| } |
| |
| static int udc_smartbond_shutdown(const struct device *dev) |
| { |
| if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { |
| LOG_ERR("Failed to disable control endpoint"); |
| return -EIO; |
| } |
| |
| if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { |
| LOG_ERR("Failed to disable control endpoint"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t check_reset_end(struct usb_smartbond_data *data, uint32_t alt_ev) |
| { |
| if (data->nfsr == NFSR_NODE_RESET) { |
| if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_RESET)) { |
| /* |
| * Could be still in reset, but since USB_M_RESET is |
| * disabled it can be also old reset state that was not |
| * cleared yet. |
| * If (after reading USB_ALTEV_REG register again) |
| * bit is cleared reset state just ended. |
| * Keep non-reset bits combined from two previous |
| * ALTEV reads and one from the next line. |
| */ |
| alt_ev = (alt_ev & ~USB_USB_ALTEV_REG_USB_RESET_Msk) | USB->USB_ALTEV_REG; |
| } |
| |
| if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_RESET) == 0) { |
| USB->USB_ALTMSK_REG = |
| USB_USB_ALTMSK_REG_USB_M_RESET_Msk | USB_USB_ALTEV_REG_USB_SD3_Msk; |
| if (data->ep_state[0][0].buf != NULL) { |
| USB->USB_MAMSK_REG |= USB_USB_MAMSK_REG_USB_M_EP0_RX_Msk; |
| } |
| LOG_DBG("Set operational %02x", USB->USB_MAMSK_REG); |
| set_nfsr(data, NFSR_NODE_OPERATIONAL); |
| } |
| } |
| return alt_ev; |
| } |
| |
| void handle_ep0_tx(struct usb_smartbond_data *data) |
| { |
| uint32_t txs0; |
| struct smartbond_ep_state *ep0_in_state = EP0_IN_STATE(data); |
| const uint8_t ep = USB_CONTROL_EP_IN; |
| struct smartbond_ep_reg_set *regs = ep0_in_state->regs; |
| struct udc_ep_config *ep0_in_config = &ep0_in_state->config; |
| struct net_buf *buf = ep0_in_state->buf; |
| bool start_next_packet = true; |
| |
| txs0 = regs->txs; |
| |
| LOG_DBG("%02x %02x", ep, txs0); |
| |
| if (GET_BIT(txs0, USB_USB_TXS0_REG_USB_TX_DONE)) { |
| /* ACK received */ |
| if (GET_BIT(txs0, USB_USB_TXS0_REG_USB_ACK_STAT)) { |
| net_buf_pull(buf, ep0_in_state->last_packet_size); |
| ep0_in_state->last_packet_size = 0; |
| ep0_in_config->stat.data1 ^= 1; |
| REG_SET_VAL(USB_TXC0_REG, USB_TOGGLE_TX0, ep0_in_config->stat.data1); |
| |
| /* |
| * Packet was sent to host but host already sent OUT packet |
| * that was NAK'ed. It means that no more data is needed. |
| */ |
| if (USB->USB_EP0_NAK_REG & USB_USB_EP0_NAK_REG_USB_EP0_OUTNAK_Msk) { |
| net_buf_pull(buf, buf->len); |
| udc_ep_buf_clear_zlp(buf); |
| } |
| if (buf->len == 0) { |
| /* When everything was sent there is not need to fill new packet */ |
| start_next_packet = false; |
| /* Send ZLP if protocol needs it */ |
| if (udc_ep_buf_has_zlp(buf)) { |
| udc_ep_buf_clear_zlp(buf); |
| /* Enable transmitter without putting anything in FIFO */ |
| USB->USB_TXC0_REG |= USB_USB_TXC0_REG_USB_TX_EN_Msk; |
| } else { |
| REG_CLR_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); |
| k_work_submit_to_queue(udc_get_work_q(), |
| &data->ep0_tx_work); |
| } |
| } |
| } else { |
| /* Start from the beginning */ |
| ep0_in_state->last_packet_size = 0; |
| } |
| if (start_next_packet) { |
| start_tx_packet(data, ep0_in_state); |
| } |
| } |
| } |
| |
| static void handle_epx_rx_ev(struct usb_smartbond_data *data, uint8_t ep_idx) |
| { |
| uint32_t rxs; |
| int fifo_bytes; |
| struct smartbond_ep_state *ep_state = usb_dc_get_ep_out_state(data, ep_idx); |
| const struct udc_smartbond_config *config = data->dev->config; |
| struct smartbond_ep_reg_set *regs = ep_state->regs; |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| struct net_buf *buf = ep_state->buf; |
| |
| do { |
| rxs = regs->rxs; |
| |
| if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_ERR)) { |
| regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; |
| ep_state->last_packet_size = 0; |
| if (data->dma_ep[0] == ep_state) { |
| /* Stop DMA */ |
| dma_stop(config->dma_cfg.rx_dev, config->dma_cfg.rx_chan); |
| /* Restart DMA since packet was dropped, |
| * all parameters should still work. |
| */ |
| dma_start(config->dma_cfg.rx_dev, config->dma_cfg.rx_chan); |
| } |
| break; |
| } |
| |
| if (data->dma_ep[0] == ep_state) { |
| struct dma_status rx_dma_status; |
| |
| dma_get_status(config->dma_cfg.rx_dev, config->dma_cfg.rx_chan, |
| &rx_dma_status); |
| /* |
| * Disable DMA and update last_packet_size |
| * with what DMA reported. |
| */ |
| dma_stop(config->dma_cfg.rx_dev, config->dma_cfg.rx_chan); |
| ep_state->last_packet_size = rx_dma_status.total_copied; |
| |
| /* |
| * When DMA did not finished (packet was smaller then MPS), |
| * dma_idx holds exact number of bytes transmitted. When DMA |
| * finished value in dma_idx is one less then actual number of |
| * transmitted bytes. |
| */ |
| if (ep_state->last_packet_size == |
| (rx_dma_status.total_copied + rx_dma_status.pending_length)) { |
| ep_state->last_packet_size++; |
| } |
| /* Release DMA to use by other endpoints. */ |
| data->dma_ep[0] = NULL; |
| } |
| fifo_bytes = GET_BIT(rxs, USB_USB_RXS1_REG_USB_RXCOUNT); |
| /* |
| * FIFO maybe empty if DMA read it before or |
| * it's final iteration and function already read all |
| * that was to read. |
| */ |
| if (fifo_bytes > 0) { |
| fifo_bytes = read_rx_fifo(ep_state, |
| net_buf_tail(buf) + ep_state->last_packet_size, |
| fifo_bytes); |
| } |
| |
| if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST)) { |
| if (!ep_state->iso && |
| GET_BIT(rxs, USB_USB_RXS1_REG_USB_TOGGLE_RX) != ep_cfg->stat.data1) { |
| /* Toggle bit does not match discard packet */ |
| regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; |
| ep_state->last_packet_size = 0; |
| LOG_WRN("Packet with incorrect data1 field rejected"); |
| /* Re-enable reception */ |
| start_rx_packet(data, ep_state); |
| } else { |
| ep_cfg->stat.data1 ^= 1; |
| REG_SET_VAL(USB_TXC0_REG, USB_TOGGLE_TX0, ep_cfg->stat.data1); |
| net_buf_add(buf, ep_state->last_packet_size); |
| |
| if (net_buf_tailroom(buf) == 0 || |
| ep_state->last_packet_size < udc_mps_ep_size(ep_cfg) || |
| ep_state->iso) { |
| buf = udc_buf_get(data->dev, ep_cfg->addr); |
| if (unlikely(buf == NULL)) { |
| LOG_ERR("ep 0x%02x queue is empty", ep_cfg->addr); |
| break; |
| } |
| ep_cfg->stat.busy = 0; |
| udc_submit_ep_event(data->dev, buf, 0); |
| break; |
| } |
| start_rx_packet(data, ep_state); |
| } |
| } |
| } while (fifo_bytes > config->fifo_read_threshold); |
| } |
| |
| static void handle_rx_ev(struct usb_smartbond_data *data) |
| { |
| if (USB->USB_RXEV_REG & BIT(0)) { |
| handle_epx_rx_ev(data, 1); |
| } |
| |
| if (USB->USB_RXEV_REG & BIT(1)) { |
| handle_epx_rx_ev(data, 2); |
| } |
| |
| if (USB->USB_RXEV_REG & BIT(2)) { |
| handle_epx_rx_ev(data, 3); |
| } |
| } |
| |
| static void handle_epx_tx_ev(struct usb_smartbond_data *data, struct smartbond_ep_state *ep_state) |
| { |
| uint32_t txs; |
| const struct udc_smartbond_config *config = data->dev->config; |
| struct smartbond_ep_reg_set *regs = ep_state->regs; |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| struct net_buf *buf = ep_state->buf; |
| const uint8_t ep = ep_cfg->addr; |
| |
| txs = regs->txs; |
| |
| if (GET_BIT(txs, USB_USB_TXS1_REG_USB_TX_DONE)) { |
| if (data->dma_ep[1] == ep_state) { |
| struct dma_status tx_dma_status; |
| |
| dma_get_status(config->dma_cfg.tx_dev, config->dma_cfg.tx_chan, |
| &tx_dma_status); |
| /* |
| * Disable DMA and update last_packet_size with what |
| * DMA reported. |
| */ |
| dma_stop(config->dma_cfg.tx_dev, config->dma_cfg.tx_chan); |
| ep_state->last_packet_size = tx_dma_status.total_copied + 1; |
| /* Release DMA to used by other endpoints. */ |
| data->dma_ep[1] = NULL; |
| } |
| |
| if (GET_BIT(txs, USB_USB_TXS1_REG_USB_ACK_STAT)) { |
| /* ACK received, update transfer state and DATA0/1 bit */ |
| net_buf_pull(buf, ep_state->last_packet_size); |
| ep_state->last_packet_size = 0; |
| ep_cfg->stat.data1 ^= 1; |
| REG_SET_VAL(USB_TXC0_REG, USB_TOGGLE_TX0, ep_cfg->stat.data1); |
| |
| if (buf->len == 0) { |
| if (udc_ep_buf_has_zlp(buf)) { |
| udc_ep_buf_clear_zlp(buf); |
| /* Enable transmitter without putting anything in FIFO */ |
| regs->txc |= USB_USB_TXC1_REG_USB_TX_EN_Msk | |
| USB_USB_TXC1_REG_USB_LAST_Msk; |
| } else { |
| udc_ep_set_busy(data->dev, ep, false); |
| buf = udc_buf_get(data->dev, ep); |
| |
| udc_submit_ep_event(data->dev, buf, 0); |
| udc_smartbond_ep_tx(data->dev, ep); |
| } |
| return; |
| } |
| } else if (regs->epc_in & USB_USB_EPC1_REG_USB_STALL_Msk) { |
| /* |
| * TX_DONE also indicates that STALL packet was just sent, |
| * there is no point to put anything into transmit FIFO. |
| * It could result in empty packet being scheduled. |
| */ |
| return; |
| } |
| } |
| |
| if (txs & USB_USB_TXS1_REG_USB_TX_URUN_Msk) { |
| LOG_DBG("EP 0x%02x FIFO under-run\n", ep); |
| } |
| /* Start next or repeated packet. */ |
| start_tx_packet(data, ep_state); |
| } |
| |
| static void handle_tx_ev(struct usb_smartbond_data *data) |
| { |
| if (USB->USB_TXEV_REG & BIT(0)) { |
| handle_epx_tx_ev(data, usb_dc_get_ep_in_state(data, 1)); |
| } |
| if (USB->USB_TXEV_REG & BIT(1)) { |
| handle_epx_tx_ev(data, usb_dc_get_ep_in_state(data, 2)); |
| } |
| if (USB->USB_TXEV_REG & BIT(2)) { |
| handle_epx_tx_ev(data, usb_dc_get_ep_in_state(data, 3)); |
| } |
| } |
| |
| static void handle_epx_tx_warn_ev(struct usb_smartbond_data *data, uint8_t ep_idx) |
| { |
| fill_tx_fifo(usb_dc_get_ep_in_state(data, ep_idx)); |
| } |
| |
| static void handle_fifo_warning(struct usb_smartbond_data *data) |
| { |
| uint32_t fifo_warning = USB->USB_FWEV_REG; |
| |
| if (fifo_warning & BIT(0)) { |
| handle_epx_tx_warn_ev(data, 1); |
| } |
| |
| if (fifo_warning & BIT(1)) { |
| handle_epx_tx_warn_ev(data, 2); |
| } |
| |
| if (fifo_warning & BIT(2)) { |
| handle_epx_tx_warn_ev(data, 3); |
| } |
| |
| if (fifo_warning & BIT(4)) { |
| handle_epx_rx_ev(data, 1); |
| } |
| |
| if (fifo_warning & BIT(5)) { |
| handle_epx_rx_ev(data, 2); |
| } |
| |
| if (fifo_warning & BIT(6)) { |
| handle_epx_rx_ev(data, 3); |
| } |
| } |
| |
| static void handle_ep0_nak(struct usb_smartbond_data *data) |
| { |
| uint32_t ep0_nak = USB->USB_EP0_NAK_REG; |
| |
| if (REG_GET_BIT(USB_EPC0_REG, USB_STALL)) { |
| if (GET_BIT(ep0_nak, USB_USB_EP0_NAK_REG_USB_EP0_INNAK)) { |
| /* |
| * EP0 is stalled and NAK was sent, it means that |
| * RX is enabled. Disable RX for now. |
| */ |
| REG_CLR_BIT(USB_RXC0_REG, USB_RX_EN); |
| REG_SET_BIT(USB_TXC0_REG, USB_TX_EN); |
| } |
| |
| if (GET_BIT(ep0_nak, USB_USB_EP0_NAK_REG_USB_EP0_OUTNAK)) { |
| REG_SET_BIT(USB_RXC0_REG, USB_RX_EN); |
| } |
| } else { |
| REG_CLR_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); |
| if (REG_GET_BIT(USB_RXC0_REG, USB_RX_EN) == 0 && |
| GET_BIT(ep0_nak, USB_USB_EP0_NAK_REG_USB_EP0_OUTNAK)) { |
| (void)USB->USB_EP0_NAK_REG; |
| k_work_submit_to_queue(udc_get_work_q(), &data->ep0_tx_work); |
| } |
| } |
| } |
| |
| static void empty_ep0_queues(const struct device *dev) |
| { |
| struct net_buf *buf; |
| |
| buf = udc_buf_get_all(dev, USB_CONTROL_EP_OUT); |
| if (buf) { |
| net_buf_unref(buf); |
| } |
| buf = udc_buf_get_all(dev, USB_CONTROL_EP_IN); |
| if (buf) { |
| net_buf_unref(buf); |
| } |
| } |
| |
| static void handle_bus_reset(struct usb_smartbond_data *data) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| uint32_t alt_ev; |
| |
| USB->USB_NFSR_REG = 0; |
| USB->USB_FAR_REG = 0x80; |
| USB->USB_ALTMSK_REG = 0; |
| USB->USB_NFSR_REG = NFSR_NODE_RESET; |
| USB->USB_TXMSK_REG = 0; |
| USB->USB_RXMSK_REG = 0; |
| set_nfsr(data, NFSR_NODE_RESET); |
| |
| for (int i = 0; i < config->num_of_eps; ++i) { |
| data->ep_state[1][i].buf = NULL; |
| data->ep_state[1][i].config.stat.busy = 0; |
| } |
| |
| LOG_INF("send USB_DC_RESET"); |
| udc_submit_event(data->dev, UDC_EVT_RESET, 0); |
| USB->USB_DMA_CTRL_REG = 0; |
| |
| USB->USB_MAMSK_REG = USB_USB_MAMSK_REG_USB_M_INTR_Msk | USB_USB_MAMSK_REG_USB_M_FRAME_Msk | |
| USB_USB_MAMSK_REG_USB_M_WARN_Msk | USB_USB_MAMSK_REG_USB_M_ALT_Msk | |
| USB_USB_MAMSK_REG_USB_M_EP0_RX_Msk | |
| USB_USB_MAMSK_REG_USB_M_EP0_TX_Msk; |
| USB->USB_ALTMSK_REG = USB_USB_ALTMSK_REG_USB_M_RESUME_Msk; |
| alt_ev = USB->USB_ALTEV_REG; |
| check_reset_end(data, alt_ev); |
| empty_ep0_queues(data->dev); |
| } |
| |
| static void usb_clock_on(struct usb_smartbond_data *data) |
| { |
| if (atomic_cas(&data->clk_requested, 0, 1)) { |
| clock_control_on(DEVICE_DT_GET(DT_NODELABEL(osc)), |
| (clock_control_subsys_rate_t)SMARTBOND_CLK_USB); |
| } |
| } |
| |
| static void usb_clock_off(struct usb_smartbond_data *data) |
| { |
| if (atomic_cas(&data->clk_requested, 1, 0)) { |
| clock_control_off(DEVICE_DT_GET(DT_NODELABEL(osc)), |
| (clock_control_subsys_rate_t)SMARTBOND_CLK_USB); |
| } |
| } |
| |
| static void handle_alt_ev(struct usb_smartbond_data *data) |
| { |
| const struct udc_smartbond_config *config = data->dev->config; |
| struct smartbond_ep_state *ep_state; |
| uint32_t alt_ev = USB->USB_ALTEV_REG; |
| |
| if (USB->USB_NFSR_REG == NFSR_NODE_SUSPEND) { |
| usb_clock_on(data); |
| } |
| alt_ev = check_reset_end(data, alt_ev); |
| if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_RESET) && data->nfsr != NFSR_NODE_RESET) { |
| handle_bus_reset(data); |
| } else if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_RESUME)) { |
| if (USB->USB_NFSR_REG == NFSR_NODE_SUSPEND) { |
| set_nfsr(data, NFSR_NODE_OPERATIONAL); |
| if (data->ep_state[0][0].buf != NULL) { |
| USB->USB_MAMSK_REG |= USB_USB_MAMSK_REG_USB_M_EP0_RX_Msk; |
| } |
| USB->USB_ALTMSK_REG = USB_USB_ALTMSK_REG_USB_M_RESET_Msk | |
| USB_USB_ALTMSK_REG_USB_M_SD3_Msk; |
| /* Re-enable reception of endpoint with pending transfer */ |
| for (int ep_idx = 1; ep_idx < config->num_of_eps; ++ep_idx) { |
| ep_state = usb_dc_get_ep_out_state(data, ep_idx); |
| if (!ep_state->config.stat.halted) { |
| start_rx_packet(data, ep_state); |
| } |
| } |
| udc_submit_event(data->dev, UDC_EVT_RESUME, 0); |
| } |
| } else if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_SD3)) { |
| set_nfsr(data, NFSR_NODE_SUSPEND); |
| USB->USB_ALTMSK_REG = |
| USB_USB_ALTMSK_REG_USB_M_RESET_Msk | USB_USB_ALTMSK_REG_USB_M_RESUME_Msk; |
| usb_clock_off(data); |
| udc_submit_event(data->dev, UDC_EVT_SUSPEND, 0); |
| } |
| } |
| |
| static void udc_smartbond_isr(const struct device *dev) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| uint32_t int_status = USB->USB_MAEV_REG & USB->USB_MAMSK_REG; |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_WARN)) { |
| handle_fifo_warning(data); |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_CH_EV)) { |
| /* For now just clear interrupt */ |
| (void)USB->USB_CHARGER_STAT_REG; |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_EP0_TX)) { |
| handle_ep0_tx(data); |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_EP0_RX)) { |
| handle_ep0_rx(data); |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_EP0_NAK)) { |
| handle_ep0_nak(data); |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_RX_EV)) { |
| handle_rx_ev(data); |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_NAK)) { |
| (void)USB->USB_NAKEV_REG; |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_FRAME)) { |
| if (data->nfsr == NFSR_NODE_RESET) { |
| /* |
| * During reset FRAME interrupt is enabled to periodically |
| * check when reset state ends. |
| * FRAME interrupt is generated every 1ms without host sending |
| * actual SOF. |
| */ |
| check_reset_end(data, USB_USB_ALTEV_REG_USB_RESET_Msk); |
| } else if (data->nfsr == NFSR_NODE_WAKING) { |
| /* No need to call set_nfsr, just set state */ |
| data->nfsr = NFSR_NODE_WAKING2; |
| } else if (data->nfsr == NFSR_NODE_WAKING2) { |
| /* No need to call set_nfsr, just set state */ |
| data->nfsr = NFSR_NODE_RESUME; |
| LOG_DBG("data->nfsr = NFSR_NODE_RESUME %02x", USB->USB_MAMSK_REG); |
| } else if (data->nfsr == NFSR_NODE_RESUME) { |
| set_nfsr(data, NFSR_NODE_OPERATIONAL); |
| if (data->ep_state[0][0].buf != NULL) { |
| USB->USB_MAMSK_REG |= USB_USB_MAMSK_REG_USB_M_EP0_RX_Msk; |
| } |
| LOG_DBG("Set operational %02x", USB->USB_MAMSK_REG); |
| } else { |
| USB->USB_MAMSK_REG &= ~USB_USB_MAMSK_REG_USB_M_FRAME_Msk; |
| } |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_TX_EV)) { |
| handle_tx_ev(data); |
| } |
| |
| if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_ALT)) { |
| handle_alt_ev(data); |
| } |
| } |
| |
| /** |
| * USB functionality can be disabled from HOST and DEVICE side. |
| * Host side is indicated by VBUS line. |
| * Device side is decided by pair of calls udc_enable()/udc_disable(), |
| * USB will only work when application calls udc_enable() and VBUS is present. |
| * When both conditions are not met USB clock (PLL) is released, and peripheral |
| * remain in reset state. |
| */ |
| static void usb_change_state(struct usb_smartbond_data *data, bool attached, bool vbus_present) |
| { |
| if (data->attached == attached && data->vbus_present == vbus_present) { |
| return; |
| } |
| |
| if (vbus_present != data->vbus_present && attached) { |
| udc_submit_event(data->dev, |
| vbus_present ? UDC_EVT_VBUS_READY : UDC_EVT_VBUS_REMOVED, 0); |
| } |
| if (attached && vbus_present) { |
| data->attached = true; |
| data->vbus_present = true; |
| /* |
| * Prevent transition to standby, this greatly reduces |
| * IRQ response time |
| */ |
| pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| usb_smartbond_dma_config(data); |
| usb_clock_on(data); |
| USB->USB_MCTRL_REG = USB_USB_MCTRL_REG_USBEN_Msk; |
| USB->USB_NFSR_REG = 0; |
| USB->USB_FAR_REG = 0x80; |
| USB->USB_TXMSK_REG = 0; |
| USB->USB_RXMSK_REG = 0; |
| |
| USB->USB_MAMSK_REG = USB_USB_MAMSK_REG_USB_M_INTR_Msk | |
| USB_USB_MAMSK_REG_USB_M_ALT_Msk | |
| USB_USB_MAMSK_REG_USB_M_WARN_Msk; |
| USB->USB_ALTMSK_REG = |
| USB_USB_ALTMSK_REG_USB_M_RESET_Msk | USB_USB_ALTEV_REG_USB_SD3_Msk; |
| |
| USB->USB_MCTRL_REG = USB_USB_MCTRL_REG_USBEN_Msk | USB_USB_MCTRL_REG_USB_NAT_Msk; |
| } else if (data->attached && data->vbus_present) { |
| /* |
| * USB was previously in use now either VBUS is gone or application |
| * requested detach, put it down |
| */ |
| data->attached = attached; |
| data->vbus_present = vbus_present; |
| /* |
| * It's imperative that USB_NAT bit-field is updated with the |
| * USBEN bit-field being set. As such, zeroing the control |
| * register at once will result in leaving the USB transceivers |
| * in a floating state. Such an action, will induce incorrect |
| * behavior for subsequent charger detection operations and given |
| * that the device does not enter the sleep state (thus powering off |
| * PD_SYS and resetting the controller along with its transceivers). |
| */ |
| REG_CLR_BIT(USB_MCTRL_REG, USB_NAT); |
| USB->USB_MCTRL_REG = 0; |
| usb_clock_off(data); |
| usb_smartbond_dma_deconfig(data); |
| /* Allow standby USB not in use or not connected */ |
| pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| } else { |
| /* USB still not activated, keep track of what's on and off */ |
| data->attached = attached; |
| data->vbus_present = vbus_present; |
| } |
| } |
| |
| static void usb_dc_smartbond_vbus_isr(struct usb_smartbond_data *data) |
| { |
| LOG_DBG("VBUS_ISR"); |
| |
| CRG_TOP->VBUS_IRQ_CLEAR_REG = 1; |
| usb_change_state(data, data->attached, |
| (CRG_TOP->ANA_STATUS_REG & CRG_TOP_ANA_STATUS_REG_VBUS_AVAILABLE_Msk) != |
| 0); |
| } |
| |
| static int usb_dc_smartbond_alloc_status_out(const struct device *dev) |
| { |
| |
| struct udc_ep_config *const ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); |
| struct net_buf *buf; |
| |
| buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, 0); |
| if (buf == NULL) { |
| return -ENOMEM; |
| } |
| |
| k_fifo_put(&ep_cfg->fifo, buf); |
| |
| return 0; |
| } |
| |
| static int usbd_ctrl_feed_dout(const struct device *dev, const size_t length) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| struct smartbond_ep_state *ep_state = EP0_OUT_STATE(data); |
| struct udc_ep_config *const ep_cfg = &ep_state->config; |
| struct net_buf *buf; |
| |
| buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); |
| if (buf == NULL) { |
| return -ENOMEM; |
| } |
| |
| k_fifo_put(&ep_cfg->fifo, buf); |
| ep_state->buf = buf; |
| start_rx_packet(data, ep_state); |
| |
| return 0; |
| } |
| |
| static void handle_ep0_rx_work(struct k_work *item) |
| { |
| struct usb_smartbond_data *data = |
| CONTAINER_OF(item, struct usb_smartbond_data, ep0_rx_work); |
| const uint8_t ep = USB_CONTROL_EP_OUT; |
| struct net_buf *buf; |
| const struct device *dev = data->dev; |
| unsigned int lock_key; |
| |
| /* |
| * Lock needed here because busy is a bit field and access |
| * may result in wrong state of data1 field |
| */ |
| lock_key = irq_lock(); |
| |
| udc_ep_set_busy(dev, ep, false); |
| buf = udc_buf_get(dev, ep); |
| |
| irq_unlock(lock_key); |
| if (unlikely(buf == NULL)) { |
| LOG_ERR("ep 0x%02x queue is empty", ep); |
| return; |
| } |
| /* Update packet size */ |
| if (udc_ctrl_stage_is_status_out(dev)) { |
| udc_ctrl_update_stage(dev, buf); |
| udc_ctrl_submit_status(dev, buf); |
| } else { |
| udc_ctrl_update_stage(dev, buf); |
| } |
| |
| if (udc_ctrl_stage_is_status_in(dev)) { |
| udc_ctrl_submit_s_out_status(dev, buf); |
| } |
| } |
| |
| static void handle_ep0_tx_work(struct k_work *item) |
| { |
| struct usb_smartbond_data *data = |
| CONTAINER_OF(item, struct usb_smartbond_data, ep0_tx_work); |
| struct net_buf *buf; |
| const struct device *dev = data->dev; |
| const uint8_t ep = USB_CONTROL_EP_IN; |
| unsigned int lock_key; |
| |
| buf = udc_buf_peek(dev, ep); |
| __ASSERT(buf == EP0_IN_STATE(data)->buf, "TX work without buffer %p %p", buf, |
| EP0_IN_STATE(data)->buf); |
| |
| /* |
| * Lock needed here because busy is a bit field and access |
| * may result in wrong state of data1 filed |
| */ |
| lock_key = irq_lock(); |
| |
| udc_ep_set_busy(dev, ep, false); |
| |
| /* Remove buffer from queue */ |
| buf = udc_buf_get(dev, ep); |
| |
| irq_unlock(lock_key); |
| |
| __ASSERT(buf == EP0_IN_STATE(data)->buf, "Internal error"); |
| |
| /* For control endpoint get ready for ACK stage |
| * from host. |
| */ |
| if (udc_ctrl_stage_is_status_in(dev) || udc_ctrl_stage_is_no_data(dev)) { |
| /* Status stage finished, notify upper layer */ |
| udc_ctrl_submit_status(dev, buf); |
| } |
| |
| /* Update to next stage of control transfer */ |
| udc_ctrl_update_stage(dev, buf); |
| |
| if (udc_ctrl_stage_is_status_out(dev)) { |
| /* |
| * Flush TX FIFO in case host already send status OUT packet |
| * and is not interested in reading from IN endpoint |
| */ |
| USB->USB_TXC0_REG = USB_USB_TXC0_REG_USB_FLUSH_Msk; |
| /* Enable reception of status OUT packet */ |
| REG_SET_BIT(USB_RXC0_REG, USB_RX_EN); |
| /* |
| * IN transfer finished, release buffer, |
| */ |
| net_buf_unref(buf); |
| } |
| } |
| |
| static void handle_ep0_setup_work(struct k_work *item) |
| { |
| struct usb_smartbond_data *data = |
| CONTAINER_OF(item, struct usb_smartbond_data, ep0_setup_work); |
| struct net_buf *buf; |
| int err; |
| const struct device *dev = data->dev; |
| struct smartbond_ep_state *ep0_out_state; |
| |
| buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, sizeof(struct usb_setup_packet)); |
| if (buf == NULL) { |
| LOG_ERR("Failed to allocate for setup"); |
| return; |
| } |
| |
| udc_ep_buf_set_setup(buf); |
| net_buf_add_mem(buf, data->setup_buffer, 8); |
| ep0_out_state = EP0_OUT_STATE(data); |
| ep0_out_state->last_packet_size = 0; |
| ep0_out_state->buf = NULL; |
| udc_ctrl_update_stage(dev, buf); |
| |
| if (udc_ctrl_stage_is_data_out(dev)) { |
| /* Allocate and feed buffer for data OUT stage */ |
| LOG_DBG("s:%p|feed for -out-", buf); |
| err = usbd_ctrl_feed_dout(dev, udc_data_stage_length(buf)); |
| if (err == -ENOMEM) { |
| err = udc_submit_ep_event(dev, buf, err); |
| } |
| } else if (udc_ctrl_stage_is_data_in(dev)) { |
| /* Allocate buffer for Status OUT state */ |
| err = usb_dc_smartbond_alloc_status_out(dev); |
| if (err == -ENOMEM) { |
| err = udc_submit_ep_event(dev, buf, err); |
| } else { |
| err = udc_ctrl_submit_s_in_status(dev); |
| if (err == -ENOMEM) { |
| err = udc_submit_ep_event(dev, buf, err); |
| } |
| } |
| } else { |
| err = udc_ctrl_submit_s_status(dev); |
| } |
| } |
| |
| static int udc_smartbond_enable(const struct device *dev) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| const struct udc_smartbond_config *config = dev->config; |
| |
| LOG_DBG("Enable UDC"); |
| |
| usb_change_state(data, true, data->vbus_present); |
| |
| if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, USB_EP_TYPE_CONTROL, 8, 0)) { |
| LOG_ERR("Failed to enable control endpoint"); |
| return -EIO; |
| } |
| |
| if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, USB_EP_TYPE_CONTROL, 8, 0)) { |
| LOG_ERR("Failed to enable control endpoint"); |
| return -EIO; |
| } |
| |
| irq_enable(config->udc_irq); |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_disable(const struct device *dev) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| const struct udc_smartbond_config *config = dev->config; |
| |
| LOG_DBG("Disable UDC"); |
| |
| usb_change_state(data, false, data->vbus_present); |
| |
| irq_disable(config->udc_irq); |
| |
| return 0; |
| } |
| |
| /* |
| * Prepare and configure most of the parts, if the controller has a way |
| * of detecting VBUS activity it should be enabled here. |
| * Only udc_smartbond_enable() makes device visible to the host. |
| */ |
| static int udc_smartbond_init(const struct device *dev) |
| { |
| struct usb_smartbond_data *data = udc_get_private(dev); |
| struct udc_data *udc_data = &data->udc_data; |
| const struct udc_smartbond_config *config = dev->config; |
| struct smartbond_ep_reg_set *reg_set = (struct smartbond_ep_reg_set *)&(USB->USB_EPC0_REG); |
| const uint16_t mps = 1023; |
| int err; |
| |
| data->dev = dev; |
| |
| k_mutex_init(&udc_data->mutex); |
| k_work_init(&data->ep0_setup_work, handle_ep0_setup_work); |
| k_work_init(&data->ep0_rx_work, handle_ep0_rx_work); |
| k_work_init(&data->ep0_tx_work, handle_ep0_tx_work); |
| |
| udc_data->caps.rwup = true; |
| udc_data->caps.mps0 = UDC_MPS0_8; |
| |
| for (int i = 0; i < config->num_of_eps; i++) { |
| data->ep_state[0][i].config.caps.out = 1; |
| if (i == 0) { |
| data->ep_state[0][i].config.caps.control = 1; |
| data->ep_state[0][i].config.caps.mps = 8; |
| } else { |
| data->ep_state[0][i].config.caps.bulk = 1; |
| data->ep_state[0][i].config.caps.interrupt = 1; |
| data->ep_state[0][i].config.caps.iso = 1; |
| data->ep_state[0][i].config.caps.mps = mps; |
| } |
| data->ep_state[0][i].config.addr = USB_EP_DIR_OUT | i; |
| err = udc_register_ep(dev, &data->ep_state[0][i].config); |
| if (err != 0) { |
| LOG_ERR("Failed to register endpoint"); |
| return err; |
| } |
| data->ep_state[0][i].regs = reg_set + i; |
| } |
| |
| for (int i = 0; i < config->num_of_eps; i++) { |
| data->ep_state[1][i].config.caps.in = 1; |
| if (i == 0) { |
| data->ep_state[1][i].config.caps.control = 1; |
| data->ep_state[1][i].config.caps.mps = 8; |
| } else { |
| data->ep_state[1][i].config.caps.bulk = 1; |
| data->ep_state[1][i].config.caps.interrupt = 1; |
| data->ep_state[1][i].config.caps.iso = 1; |
| data->ep_state[1][i].config.caps.mps = mps; |
| } |
| |
| data->ep_state[1][i].config.addr = USB_EP_DIR_IN | i; |
| err = udc_register_ep(dev, &data->ep_state[1][i].config); |
| if (err != 0) { |
| LOG_ERR("Failed to register endpoint"); |
| return err; |
| } |
| data->ep_state[1][i].regs = reg_set + i; |
| } |
| |
| CRG_TOP->VBUS_IRQ_CLEAR_REG = 1; |
| /* Both connect and disconnect needs to be handled */ |
| CRG_TOP->VBUS_IRQ_MASK_REG = CRG_TOP_VBUS_IRQ_MASK_REG_VBUS_IRQ_EN_FALL_Msk | |
| CRG_TOP_VBUS_IRQ_MASK_REG_VBUS_IRQ_EN_RISE_Msk; |
| NVIC_SetPendingIRQ(config->vbus_irq); |
| irq_enable(config->vbus_irq); |
| |
| return 0; |
| } |
| |
| static int udc_smartbond_lock(const struct device *dev) |
| { |
| return udc_lock_internal(dev, K_FOREVER); |
| } |
| |
| static int udc_smartbond_unlock(const struct device *dev) |
| { |
| return udc_unlock_internal(dev); |
| } |
| |
| static const struct udc_api udc_smartbond_api = { |
| .lock = udc_smartbond_lock, |
| .unlock = udc_smartbond_unlock, |
| .device_speed = udc_smartbond_device_speed, |
| .init = udc_smartbond_init, |
| .enable = udc_smartbond_enable, |
| .disable = udc_smartbond_disable, |
| .shutdown = udc_smartbond_shutdown, |
| .set_address = udc_smartbond_set_address, |
| .host_wakeup = udc_smartbond_host_wakeup, |
| .ep_enable = udc_smartbond_ep_enable, |
| .ep_disable = udc_smartbond_ep_disable, |
| .ep_set_halt = udc_smartbond_ep_set_halt, |
| .ep_clear_halt = udc_smartbond_ep_clear_halt, |
| .ep_enqueue = udc_smartbond_ep_enqueue, |
| .ep_dequeue = udc_smartbond_ep_dequeue, |
| }; |
| |
| #define DT_DRV_COMPAT renesas_smartbond_usbd |
| |
| #define UDC_IRQ(inst) DT_INST_IRQ_BY_IDX(inst, 0, irq) |
| #define UDC_IRQ_PRI(inst) DT_INST_IRQ_BY_IDX(inst, 0, priority) |
| #define VBUS_IRQ(inst) DT_INST_IRQ_BY_IDX(inst, 1, irq) |
| #define VBUS_IRQ_PRI(inst) DT_INST_IRQ_BY_IDX(inst, 1, priority) |
| |
| /* |
| * Minimal transfer size needed to use DMA. For short transfers |
| * it may be simpler to just fill hardware FIFO with data instead |
| * of programming DMA registers. |
| */ |
| #define DMA_MIN_TRANSFER_SIZE(inst) DT_INST_PROP(inst, dma_min_transfer_size) |
| #define FIFO_READ_THRESHOLD(inst) DT_INST_PROP(inst, fifo_read_threshold) |
| |
| #define UDC_SMARTBOND_DEVICE_DEFINE(n) \ |
| \ |
| static const struct udc_smartbond_config udc_smartbond_cfg_##n = { \ |
| .udc_irq = UDC_IRQ(n), \ |
| .vbus_irq = VBUS_IRQ(n), \ |
| .dma_min_transfer_size = DMA_MIN_TRANSFER_SIZE(n), \ |
| .fifo_read_threshold = FIFO_READ_THRESHOLD(n), \ |
| .fifo_read_threshold = FIFO_READ_THRESHOLD(n), \ |
| .num_of_eps = DT_INST_PROP(n, num_bidir_endpoints), \ |
| .dma_cfg = { \ |
| .tx_chan = DT_INST_DMAS_CELL_BY_NAME(n, tx, channel), \ |
| .tx_slot_mux = DT_INST_DMAS_CELL_BY_NAME(n, tx, config), \ |
| .tx_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(n, tx)), \ |
| .rx_chan = DT_INST_DMAS_CELL_BY_NAME(n, rx, channel), \ |
| .rx_slot_mux = DT_INST_DMAS_CELL_BY_NAME(n, rx, config), \ |
| .rx_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(n, rx)), \ |
| }, \ |
| }; \ |
| \ |
| static struct usb_smartbond_data udc_data_##n = { \ |
| .udc_data = { \ |
| .mutex = Z_MUTEX_INITIALIZER(udc_data_##n.udc_data.mutex), \ |
| .priv = &udc_data_##n, \ |
| }, \ |
| }; \ |
| \ |
| static int udc_smartbond_driver_preinit_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(VBUS_IRQ(n), VBUS_IRQ_PRI(n), usb_dc_smartbond_vbus_isr, \ |
| &udc_data_##n, 0); \ |
| IRQ_CONNECT(UDC_IRQ(n), UDC_IRQ_PRI(n), udc_smartbond_isr, DEVICE_DT_INST_GET(n), \ |
| 0); \ |
| return 0; \ |
| } \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, udc_smartbond_driver_preinit_##n, NULL, &udc_data_##n, \ |
| &udc_smartbond_cfg_##n, POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &udc_smartbond_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(UDC_SMARTBOND_DEVICE_DEFINE) |