1065 lines
24 KiB
C
1065 lines
24 KiB
C
/* usb_dc_kinetis.c - Kinetis USBFSOTG usb device driver */
|
|
|
|
/*
|
|
* Copyright (c) 2017 PHYTEC Messtechnik GmbH
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT nxp_kinetis_usbd
|
|
|
|
#include <soc.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/usb/usb_device.h>
|
|
#include <zephyr/init.h>
|
|
|
|
#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(usb_dc_kinetis);
|
|
|
|
#define NUM_OF_EP_MAX DT_INST_PROP(0, num_bidir_endpoints)
|
|
|
|
#define BD_OWN_MASK (1 << 5)
|
|
#define BD_DATA01_MASK (1 << 4)
|
|
#define BD_KEEP_MASK (1 << 3)
|
|
#define BD_NINC_MASK (1 << 2)
|
|
#define BD_DTS_MASK (1 << 1)
|
|
#define BD_STALL_MASK (1 << 0)
|
|
|
|
#define KINETIS_SETUP_TOKEN 0x0d
|
|
#define KINETIS_IN_TOKEN 0x09
|
|
#define KINETIS_OUT_TOKEN 0x01
|
|
|
|
#define USBFSOTG_PERID 0x04
|
|
#define USBFSOTG_REV 0x33
|
|
|
|
#define KINETIS_EP_NUMOF_MASK 0xf
|
|
#define KINETIS_ADDR2IDX(addr) ((addr) & (KINETIS_EP_NUMOF_MASK))
|
|
|
|
/*
|
|
* Buffer Descriptor (BD) entry provides endpoint buffer control
|
|
* information for USBFS controller. Every endpoint direction requires
|
|
* two BD entries.
|
|
*/
|
|
struct buf_descriptor {
|
|
union {
|
|
uint32_t bd_fields;
|
|
|
|
struct {
|
|
uint32_t reserved_1_0 : 2;
|
|
uint32_t tok_pid : 4;
|
|
uint32_t data01 : 1;
|
|
uint32_t own : 1;
|
|
uint32_t reserved_15_8 : 8;
|
|
uint32_t bc : 16;
|
|
} get __packed;
|
|
|
|
struct {
|
|
uint32_t reserved_1_0 : 2;
|
|
uint32_t bd_ctrl : 6;
|
|
uint32_t reserved_15_8 : 8;
|
|
uint32_t bc : 16;
|
|
} set __packed;
|
|
|
|
} __packed;
|
|
uint32_t buf_addr;
|
|
} __packed;
|
|
|
|
/*
|
|
* Buffer Descriptor Table for the endpoints buffer management.
|
|
* The driver configuration with 16 fully bidirectional endpoints would require
|
|
* four BD entries per endpoint and 512 bytes of memory.
|
|
*/
|
|
static struct buf_descriptor __aligned(512) bdt[(NUM_OF_EP_MAX) * 2 * 2];
|
|
|
|
#define BD_IDX_EP0TX_EVEN 2
|
|
#define BD_IDX_EP0TX_ODD 3
|
|
|
|
#define EP_BUF_NUMOF_BLOCKS (NUM_OF_EP_MAX / 2)
|
|
|
|
K_HEAP_DEFINE(ep_buf_pool, 512 * EP_BUF_NUMOF_BLOCKS + 128);
|
|
|
|
struct ep_mem_block {
|
|
void *data;
|
|
};
|
|
|
|
struct usb_ep_ctrl_data {
|
|
struct ep_status {
|
|
uint16_t in_enabled : 1;
|
|
uint16_t out_enabled : 1;
|
|
uint16_t in_data1 : 1;
|
|
uint16_t out_data1 : 1;
|
|
uint16_t in_odd : 1;
|
|
uint16_t out_odd : 1;
|
|
uint16_t in_stalled : 1;
|
|
uint16_t out_stalled : 1;
|
|
} status;
|
|
uint16_t mps_in;
|
|
uint16_t mps_out;
|
|
struct ep_mem_block mblock_in;
|
|
struct ep_mem_block mblock_out;
|
|
usb_dc_ep_callback cb_in;
|
|
usb_dc_ep_callback cb_out;
|
|
};
|
|
|
|
#define USBD_THREAD_STACK_SIZE 1024
|
|
|
|
struct usb_device_data {
|
|
usb_dc_status_callback status_cb;
|
|
uint8_t address;
|
|
uint32_t bd_active;
|
|
struct usb_ep_ctrl_data ep_ctrl[NUM_OF_EP_MAX];
|
|
bool attached;
|
|
|
|
K_KERNEL_STACK_MEMBER(thread_stack, USBD_THREAD_STACK_SIZE);
|
|
struct k_thread thread;
|
|
};
|
|
|
|
static struct usb_device_data dev_data;
|
|
|
|
#define USB_DC_CB_TYPE_MGMT 0
|
|
#define USB_DC_CB_TYPE_EP 1
|
|
|
|
struct cb_msg {
|
|
uint8_t ep;
|
|
uint8_t type;
|
|
uint32_t cb;
|
|
};
|
|
|
|
K_MSGQ_DEFINE(usb_dc_msgq, sizeof(struct cb_msg), 10, 4);
|
|
static void usb_kinetis_isr_handler(void);
|
|
|
|
/*
|
|
* This function returns the BD element index based on
|
|
* endpoint address and the odd bit.
|
|
*/
|
|
static inline uint8_t get_bdt_idx(uint8_t ep, uint8_t odd)
|
|
{
|
|
if (ep & USB_EP_DIR_IN) {
|
|
return ((((KINETIS_ADDR2IDX(ep)) * 4) + 2 + (odd & 1)));
|
|
}
|
|
return ((((KINETIS_ADDR2IDX(ep)) * 4) + (odd & 1)));
|
|
}
|
|
|
|
static int kinetis_usb_init(void)
|
|
{
|
|
#if !DT_INST_PROP(0, no_voltage_regulator)
|
|
/* enable USB voltage regulator */
|
|
SIM->SOPT1 |= SIM_SOPT1_USBREGEN_MASK;
|
|
#endif
|
|
|
|
USB0->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
|
|
k_busy_wait(2000);
|
|
|
|
USB0->CTL = 0;
|
|
/* enable USB module, AKA USBEN bit in CTL1 register */
|
|
USB0->CTL |= USB_CTL_USBENSOFEN_MASK;
|
|
|
|
if ((USB0->PERID != USBFSOTG_PERID) ||
|
|
(USB0->REV != USBFSOTG_REV)) {
|
|
return -1;
|
|
}
|
|
|
|
USB0->BDTPAGE1 = (uint8_t)(((uint32_t)bdt) >> 8);
|
|
USB0->BDTPAGE2 = (uint8_t)(((uint32_t)bdt) >> 16);
|
|
USB0->BDTPAGE3 = (uint8_t)(((uint32_t)bdt) >> 24);
|
|
|
|
/* clear interrupt flags */
|
|
USB0->ISTAT = 0xFF;
|
|
|
|
/* enable reset interrupt */
|
|
USB0->INTEN = USB_INTEN_USBRSTEN_MASK;
|
|
|
|
USB0->USBCTRL = USB_USBCTRL_PDE_MASK;
|
|
|
|
|
|
|
|
LOG_DBG("");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_reset(void)
|
|
{
|
|
for (uint8_t i = 0; i < 16; i++) {
|
|
USB0->ENDPOINT[i].ENDPT = 0;
|
|
}
|
|
dev_data.bd_active = 0U;
|
|
dev_data.address = 0U;
|
|
|
|
USB0->CTL |= USB_CTL_ODDRST_MASK;
|
|
USB0->CTL &= ~USB_CTL_ODDRST_MASK;
|
|
|
|
/* Clear interrupt status flags */
|
|
USB0->ISTAT = 0xFF;
|
|
/* Clear error flags */
|
|
USB0->ERRSTAT = 0xFF;
|
|
/* Enable all error interrupt sources */
|
|
USB0->ERREN = 0xFF;
|
|
/* Reset default address */
|
|
USB0->ADDR = 0x00;
|
|
|
|
USB0->INTEN = (USB_INTEN_USBRSTEN_MASK |
|
|
USB_INTEN_TOKDNEEN_MASK |
|
|
USB_INTEN_SLEEPEN_MASK |
|
|
USB_INTEN_SOFTOKEN_MASK |
|
|
USB_INTEN_STALLEN_MASK |
|
|
USB_INTEN_ERROREN_MASK);
|
|
|
|
LOG_DBG("");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_attach(void)
|
|
{
|
|
if (dev_data.attached) {
|
|
LOG_WRN("already attached");
|
|
}
|
|
|
|
kinetis_usb_init();
|
|
|
|
/*
|
|
* Call usb_dc_reset here because the device stack does not make it
|
|
* after USB_DC_RESET status event.
|
|
*/
|
|
usb_dc_reset();
|
|
|
|
dev_data.attached = 1;
|
|
LOG_DBG("attached");
|
|
|
|
/* non-OTG device mode, enable DP Pullup */
|
|
USB0->CONTROL = USB_CONTROL_DPPULLUPNONOTG_MASK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_detach(void)
|
|
{
|
|
LOG_DBG("");
|
|
/* disable USB and DP Pullup */
|
|
USB0->CTL &= ~USB_CTL_USBENSOFEN_MASK;
|
|
USB0->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_set_address(const uint8_t addr)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
if (!dev_data.attached) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* The device stack tries to set the address before
|
|
* sending the ACK with ZLP, which is totally stupid,
|
|
* as workaround the address will be buffered and
|
|
* placed later inside isr handler (see KINETIS_IN_TOKEN).
|
|
*/
|
|
dev_data.address = 0x80 | (addr & 0x7f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("endpoint index/address out of range");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (cfg->ep_type) {
|
|
case USB_DC_EP_CONTROL:
|
|
if (cfg->ep_mps > USB_MAX_CTRL_MPS) {
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
case USB_DC_EP_BULK:
|
|
if (cfg->ep_mps > USB_MAX_FS_BULK_MPS) {
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case USB_DC_EP_INTERRUPT:
|
|
if (cfg->ep_mps > USB_MAX_FS_INT_MPS) {
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case USB_DC_EP_ISOCHRONOUS:
|
|
if (cfg->ep_mps > USB_MAX_FS_ISO_MPS) {
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
LOG_ERR("Unknown endpoint type!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ep_idx & BIT(0)) {
|
|
if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_IN) {
|
|
LOG_INF("pre-selected as IN endpoint");
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_OUT) {
|
|
LOG_INF("pre-selected as OUT endpoint");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const cfg)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr);
|
|
struct usb_ep_ctrl_data *ep_ctrl;
|
|
struct ep_mem_block *block;
|
|
uint8_t idx_even;
|
|
uint8_t idx_odd;
|
|
|
|
if (usb_dc_ep_check_cap(cfg)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
idx_even = get_bdt_idx(cfg->ep_addr, 0);
|
|
idx_odd = get_bdt_idx(cfg->ep_addr, 1);
|
|
ep_ctrl = &dev_data.ep_ctrl[ep_idx];
|
|
|
|
if (ep_idx && (dev_data.ep_ctrl[ep_idx].status.in_enabled ||
|
|
dev_data.ep_ctrl[ep_idx].status.out_enabled)) {
|
|
LOG_WRN("endpoint already configured");
|
|
return -EALREADY;
|
|
}
|
|
|
|
LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps,
|
|
cfg->ep_type);
|
|
|
|
if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) {
|
|
block = &(ep_ctrl->mblock_out);
|
|
} else {
|
|
block = &(ep_ctrl->mblock_in);
|
|
}
|
|
|
|
if (bdt[idx_even].buf_addr) {
|
|
k_heap_free(&ep_buf_pool, block->data);
|
|
}
|
|
|
|
USB0->ENDPOINT[ep_idx].ENDPT = 0;
|
|
(void)memset(&bdt[idx_even], 0, sizeof(struct buf_descriptor));
|
|
(void)memset(&bdt[idx_odd], 0, sizeof(struct buf_descriptor));
|
|
|
|
block->data = k_heap_alloc(&ep_buf_pool, cfg->ep_mps * 2U, K_NO_WAIT);
|
|
if (block->data != NULL) {
|
|
(void)memset(block->data, 0, cfg->ep_mps * 2U);
|
|
} else {
|
|
LOG_ERR("Memory allocation time-out");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bdt[idx_even].buf_addr = (uint32_t)block->data;
|
|
LOG_INF("idx_even %x", (uint32_t)block->data);
|
|
bdt[idx_odd].buf_addr = (uint32_t)((uint8_t *)block->data + cfg->ep_mps);
|
|
LOG_INF("idx_odd %x", (uint32_t)((uint8_t *)block->data + cfg->ep_mps));
|
|
|
|
if (cfg->ep_addr & USB_EP_DIR_IN) {
|
|
dev_data.ep_ctrl[ep_idx].mps_in = cfg->ep_mps;
|
|
} else {
|
|
dev_data.ep_ctrl[ep_idx].mps_out = cfg->ep_mps;
|
|
}
|
|
|
|
bdt[idx_even].set.bc = cfg->ep_mps;
|
|
bdt[idx_odd].set.bc = cfg->ep_mps;
|
|
|
|
dev_data.ep_ctrl[ep_idx].status.out_data1 = false;
|
|
dev_data.ep_ctrl[ep_idx].status.in_data1 = false;
|
|
|
|
switch (cfg->ep_type) {
|
|
case USB_DC_EP_CONTROL:
|
|
LOG_DBG("configure control endpoint");
|
|
USB0->ENDPOINT[ep_idx].ENDPT |= (USB_ENDPT_EPHSHK_MASK |
|
|
USB_ENDPT_EPRXEN_MASK |
|
|
USB_ENDPT_EPTXEN_MASK);
|
|
break;
|
|
case USB_DC_EP_BULK:
|
|
case USB_DC_EP_INTERRUPT:
|
|
USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPHSHK_MASK;
|
|
if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) {
|
|
USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPRXEN_MASK;
|
|
} else {
|
|
USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPTXEN_MASK;
|
|
}
|
|
break;
|
|
case USB_DC_EP_ISOCHRONOUS:
|
|
if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) {
|
|
USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPRXEN_MASK;
|
|
} else {
|
|
USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPTXEN_MASK;
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_set_stall(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint8_t bd_idx;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("ep %x, idx %d", ep, ep_idx);
|
|
|
|
if (USB_EP_DIR_IS_OUT(ep)) {
|
|
dev_data.ep_ctrl[ep_idx].status.out_stalled = 1U;
|
|
bd_idx = get_bdt_idx(ep,
|
|
~dev_data.ep_ctrl[ep_idx].status.out_odd);
|
|
} else {
|
|
dev_data.ep_ctrl[ep_idx].status.in_stalled = 1U;
|
|
bd_idx = get_bdt_idx(ep,
|
|
dev_data.ep_ctrl[ep_idx].status.in_odd);
|
|
}
|
|
|
|
bdt[bd_idx].set.bd_ctrl = BD_STALL_MASK | BD_DTS_MASK | BD_OWN_MASK;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_clear_stall(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint8_t bd_idx;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("ep %x, idx %d", ep, ep_idx);
|
|
USB0->ENDPOINT[ep_idx].ENDPT &= ~USB_ENDPT_EPSTALL_MASK;
|
|
|
|
if (USB_EP_DIR_IS_OUT(ep)) {
|
|
dev_data.ep_ctrl[ep_idx].status.out_stalled = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.out_data1 = false;
|
|
bd_idx = get_bdt_idx(ep,
|
|
~dev_data.ep_ctrl[ep_idx].status.out_odd);
|
|
bdt[bd_idx].set.bd_ctrl = 0U;
|
|
bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK;
|
|
} else {
|
|
dev_data.ep_ctrl[ep_idx].status.in_stalled = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.in_data1 = false;
|
|
bd_idx = get_bdt_idx(ep,
|
|
dev_data.ep_ctrl[ep_idx].status.in_odd);
|
|
bdt[bd_idx].set.bd_ctrl = 0U;
|
|
}
|
|
|
|
/* Resume TX token processing, see USBx_CTL field descriptions */
|
|
if (ep == 0U) {
|
|
USB0->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("ep %x, idx %d", ep_idx, ep);
|
|
if (!stalled) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*stalled = 0U;
|
|
if (USB_EP_DIR_IS_OUT(ep)) {
|
|
*stalled = dev_data.ep_ctrl[ep_idx].status.out_stalled;
|
|
} else {
|
|
*stalled = dev_data.ep_ctrl[ep_idx].status.in_stalled;
|
|
}
|
|
|
|
uint8_t bd_idx = get_bdt_idx(ep,
|
|
dev_data.ep_ctrl[ep_idx].status.in_odd);
|
|
LOG_WRN("active bd ctrl: %x", bdt[bd_idx].set.bd_ctrl);
|
|
bd_idx = get_bdt_idx(ep,
|
|
~dev_data.ep_ctrl[ep_idx].status.in_odd);
|
|
LOG_WRN("next bd ctrl: %x", bdt[bd_idx].set.bd_ctrl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_halt(const uint8_t ep)
|
|
{
|
|
return usb_dc_ep_set_stall(ep);
|
|
}
|
|
|
|
int usb_dc_ep_enable(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint8_t idx_even;
|
|
uint8_t idx_odd;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
idx_even = get_bdt_idx(ep, 0);
|
|
idx_odd = get_bdt_idx(ep, 1);
|
|
|
|
if (ep_idx && (dev_data.ep_ctrl[ep_idx].status.in_enabled ||
|
|
dev_data.ep_ctrl[ep_idx].status.out_enabled)) {
|
|
LOG_WRN("endpoint 0x%x already enabled", ep);
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (USB_EP_DIR_IS_OUT(ep)) {
|
|
bdt[idx_even].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK;
|
|
bdt[idx_odd].set.bd_ctrl = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.out_odd = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.out_stalled = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.out_data1 = false;
|
|
dev_data.ep_ctrl[ep_idx].status.out_enabled = true;
|
|
} else {
|
|
bdt[idx_even].bd_fields = 0U;
|
|
bdt[idx_odd].bd_fields = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.in_odd = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.in_stalled = 0U;
|
|
dev_data.ep_ctrl[ep_idx].status.in_data1 = false;
|
|
dev_data.ep_ctrl[ep_idx].status.in_enabled = true;
|
|
}
|
|
|
|
LOG_INF("ep 0x%x, ep_idx %d", ep, ep_idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_disable(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint8_t idx_even;
|
|
uint8_t idx_odd;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
idx_even = get_bdt_idx(ep, 0);
|
|
idx_odd = get_bdt_idx(ep, 1);
|
|
|
|
LOG_INF("ep %x, idx %d", ep_idx, ep);
|
|
|
|
bdt[idx_even].bd_fields = 0U;
|
|
bdt[idx_odd].bd_fields = 0U;
|
|
if (USB_EP_DIR_IS_OUT(ep)) {
|
|
dev_data.ep_ctrl[ep_idx].status.out_enabled = false;
|
|
} else {
|
|
dev_data.ep_ctrl[ep_idx].status.in_enabled = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_flush(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("ep %x, idx %d", ep_idx, ep);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data,
|
|
const uint32_t data_len, uint32_t * const ret_bytes)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint32_t len_to_send = data_len;
|
|
uint8_t odd;
|
|
uint8_t bd_idx;
|
|
uint8_t *bufp;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
odd = dev_data.ep_ctrl[ep_idx].status.in_odd;
|
|
bd_idx = get_bdt_idx(ep, odd);
|
|
bufp = (uint8_t *)bdt[bd_idx].buf_addr;
|
|
|
|
if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN) {
|
|
LOG_ERR("Wrong endpoint direction");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dev_data.ep_ctrl[ep_idx].status.in_stalled) {
|
|
LOG_WRN("endpoint is stalled");
|
|
return -EBUSY;
|
|
}
|
|
|
|
while (bdt[bd_idx].get.own) {
|
|
LOG_DBG("ep 0x%x is busy", ep);
|
|
k_yield();
|
|
}
|
|
|
|
LOG_DBG("bd idx %x bufp %p odd %d", bd_idx, bufp, odd);
|
|
|
|
if (data_len > dev_data.ep_ctrl[ep_idx].mps_in) {
|
|
len_to_send = dev_data.ep_ctrl[ep_idx].mps_in;
|
|
}
|
|
|
|
bdt[bd_idx].set.bc = len_to_send;
|
|
|
|
for (uint32_t n = 0; n < len_to_send; n++) {
|
|
bufp[n] = data[n];
|
|
}
|
|
|
|
dev_data.ep_ctrl[ep_idx].status.in_odd = ~odd;
|
|
if (dev_data.ep_ctrl[ep_idx].status.in_data1) {
|
|
bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK |
|
|
BD_DATA01_MASK |
|
|
BD_OWN_MASK;
|
|
} else {
|
|
bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK;
|
|
}
|
|
|
|
/* Toggle next Data1 */
|
|
dev_data.ep_ctrl[ep_idx].status.in_data1 ^= 1;
|
|
|
|
LOG_DBG("ep 0x%x write %d bytes from %d", ep, len_to_send, data_len);
|
|
|
|
if (ret_bytes) {
|
|
*ret_bytes = len_to_send;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len,
|
|
uint32_t *read_bytes)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint32_t data_len;
|
|
uint8_t bd_idx;
|
|
uint8_t *bufp;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* select the index of active endpoint buffer */
|
|
bd_idx = get_bdt_idx(ep, dev_data.ep_ctrl[ep_idx].status.out_odd);
|
|
bufp = (uint8_t *)bdt[bd_idx].buf_addr;
|
|
|
|
if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) {
|
|
LOG_ERR("Wrong endpoint direction");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dev_data.ep_ctrl[ep_idx].status.out_stalled) {
|
|
LOG_WRN("endpoint is stalled");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Allow to read 0 bytes */
|
|
if (!data && max_data_len) {
|
|
LOG_ERR("Wrong arguments");
|
|
return -EINVAL;
|
|
}
|
|
|
|
while (bdt[bd_idx].get.own) {
|
|
LOG_ERR("Endpoint is occupied by the controller");
|
|
return -EBUSY;
|
|
}
|
|
|
|
data_len = bdt[bd_idx].get.bc;
|
|
|
|
if (!data && !max_data_len) {
|
|
/*
|
|
* When both buffer and max data to read are zero return
|
|
* the available data in buffer.
|
|
*/
|
|
if (read_bytes) {
|
|
*read_bytes = data_len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (data_len > max_data_len) {
|
|
LOG_WRN("Not enough room to copy all the data!");
|
|
data_len = max_data_len;
|
|
}
|
|
|
|
if (data != NULL) {
|
|
for (uint32_t i = 0; i < data_len; i++) {
|
|
data[i] = bufp[i];
|
|
}
|
|
}
|
|
|
|
LOG_DBG("Read idx %d, req %d, read %d bytes", bd_idx, max_data_len,
|
|
data_len);
|
|
|
|
if (read_bytes) {
|
|
*read_bytes = data_len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int usb_dc_ep_read_continue(uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
uint8_t bd_idx;
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
bd_idx = get_bdt_idx(ep, dev_data.ep_ctrl[ep_idx].status.out_odd);
|
|
|
|
if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) {
|
|
LOG_ERR("Wrong endpoint direction");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bdt[bd_idx].get.own) {
|
|
/* May occur when usb_transfer initializes the OUT transfer */
|
|
LOG_WRN("Current buffer is claimed by the controller");
|
|
return 0;
|
|
}
|
|
|
|
/* select the index of the next endpoint buffer */
|
|
bd_idx = get_bdt_idx(ep, ~dev_data.ep_ctrl[ep_idx].status.out_odd);
|
|
/* Update next toggle bit */
|
|
dev_data.ep_ctrl[ep_idx].status.out_data1 ^= 1;
|
|
bdt[bd_idx].set.bc = dev_data.ep_ctrl[ep_idx].mps_out;
|
|
|
|
/* Reset next buffer descriptor and set next toggle bit */
|
|
if (dev_data.ep_ctrl[ep_idx].status.out_data1) {
|
|
bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK |
|
|
BD_DATA01_MASK |
|
|
BD_OWN_MASK;
|
|
} else {
|
|
bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK;
|
|
}
|
|
|
|
/* Resume TX token processing, see USBx_CTL field descriptions */
|
|
if (ep_idx == 0U) {
|
|
USB0->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK;
|
|
}
|
|
|
|
LOG_DBG("idx next %x", bd_idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_read(const uint8_t ep, uint8_t *const data,
|
|
const uint32_t max_data_len, uint32_t *const read_bytes)
|
|
{
|
|
int retval = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes);
|
|
|
|
if (retval) {
|
|
return retval;
|
|
}
|
|
|
|
if (!data && !max_data_len) {
|
|
/* When both buffer and max data to read are zero the above
|
|
* call would fetch the data len and we simply return.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
if (usb_dc_ep_read_continue(ep) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!dev_data.attached) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ep & USB_EP_DIR_IN) {
|
|
dev_data.ep_ctrl[ep_idx].cb_in = cb;
|
|
} else {
|
|
dev_data.ep_ctrl[ep_idx].cb_out = cb;
|
|
}
|
|
LOG_DBG("ep_idx %x", ep_idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usb_dc_set_status_callback(const usb_dc_status_callback cb)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
dev_data.status_cb = cb;
|
|
}
|
|
|
|
int usb_dc_ep_mps(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (ep_idx > (NUM_OF_EP_MAX - 1)) {
|
|
LOG_ERR("Wrong endpoint index/address");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ep & USB_EP_DIR_IN) {
|
|
return dev_data.ep_ctrl[ep_idx].mps_in;
|
|
} else {
|
|
return dev_data.ep_ctrl[ep_idx].mps_out;
|
|
}
|
|
}
|
|
|
|
static inline void reenable_control_endpoints(void)
|
|
{
|
|
struct usb_dc_ep_cfg_data ep_cfg;
|
|
|
|
/* Reconfigure control endpoint 0 after a reset */
|
|
ep_cfg.ep_addr = USB_CONTROL_EP_OUT;
|
|
ep_cfg.ep_mps = USB_CONTROL_EP_MPS;
|
|
ep_cfg.ep_type = USB_DC_EP_CONTROL;
|
|
usb_dc_ep_configure(&ep_cfg);
|
|
ep_cfg.ep_addr = USB_CONTROL_EP_IN;
|
|
usb_dc_ep_configure(&ep_cfg);
|
|
|
|
/* Enable both endpoint directions */
|
|
usb_dc_ep_enable(USB_CONTROL_EP_OUT);
|
|
usb_dc_ep_enable(USB_CONTROL_EP_IN);
|
|
}
|
|
|
|
static void usb_kinetis_isr_handler(void)
|
|
{
|
|
uint8_t istatus = USB0->ISTAT;
|
|
uint8_t status = USB0->STAT;
|
|
struct cb_msg msg;
|
|
|
|
|
|
if (istatus & USB_ISTAT_USBRST_MASK) {
|
|
dev_data.address = 0U;
|
|
USB0->ADDR = (uint8_t)0;
|
|
/*
|
|
* Device reset is not possible because the stack does not
|
|
* configure the endpoints after the USB_DC_RESET event,
|
|
* therefore, we must re-enable the default control 0 endpoint
|
|
* after a reset event
|
|
*/
|
|
USB0->CTL |= USB_CTL_ODDRST_MASK;
|
|
USB0->CTL &= ~USB_CTL_ODDRST_MASK;
|
|
reenable_control_endpoints();
|
|
msg.ep = 0U;
|
|
msg.type = USB_DC_CB_TYPE_MGMT;
|
|
msg.cb = USB_DC_RESET;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
}
|
|
|
|
if (istatus == USB_ISTAT_ERROR_MASK) {
|
|
USB0->ERRSTAT = 0xFF;
|
|
msg.ep = 0U;
|
|
msg.type = USB_DC_CB_TYPE_MGMT;
|
|
msg.cb = USB_DC_ERROR;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
}
|
|
|
|
if (istatus & USB_ISTAT_STALL_MASK) {
|
|
if (dev_data.ep_ctrl[0].status.out_stalled) {
|
|
usb_dc_ep_clear_stall(0);
|
|
}
|
|
if (dev_data.ep_ctrl[0].status.in_stalled) {
|
|
usb_dc_ep_clear_stall(0x80);
|
|
}
|
|
}
|
|
|
|
if (istatus & USB_ISTAT_TOKDNE_MASK) {
|
|
|
|
uint8_t ep_idx = status >> USB_STAT_ENDP_SHIFT;
|
|
uint8_t ep = ((status << 4) & USB_EP_DIR_IN) | ep_idx;
|
|
uint8_t odd = (status & USB_STAT_ODD_MASK) >> USB_STAT_ODD_SHIFT;
|
|
uint8_t idx = get_bdt_idx(ep, odd);
|
|
uint8_t token_pid = bdt[idx].get.tok_pid;
|
|
|
|
msg.ep = ep;
|
|
msg.type = USB_DC_CB_TYPE_EP;
|
|
|
|
switch (token_pid) {
|
|
case KINETIS_SETUP_TOKEN:
|
|
dev_data.ep_ctrl[ep_idx].status.out_odd = odd;
|
|
/* clear tx entries */
|
|
bdt[BD_IDX_EP0TX_EVEN].bd_fields = 0U;
|
|
bdt[BD_IDX_EP0TX_ODD].bd_fields = 0U;
|
|
/*
|
|
* Set/Reset here the toggle bits for control endpoint
|
|
* because the device stack does not care about it.
|
|
*/
|
|
dev_data.ep_ctrl[ep_idx].status.in_data1 = true;
|
|
dev_data.ep_ctrl[ep_idx].status.out_data1 = false;
|
|
dev_data.ep_ctrl[ep_idx].status.out_odd = odd;
|
|
|
|
msg.cb = USB_DC_EP_SETUP;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
break;
|
|
case KINETIS_OUT_TOKEN:
|
|
dev_data.ep_ctrl[ep_idx].status.out_odd = odd;
|
|
|
|
msg.cb = USB_DC_EP_DATA_OUT;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
break;
|
|
case KINETIS_IN_TOKEN:
|
|
/* SET ADDRESS workaround */
|
|
if (dev_data.address & 0x80) {
|
|
USB0->ADDR = dev_data.address & 0x7f;
|
|
dev_data.address = 0U;
|
|
}
|
|
|
|
msg.cb = USB_DC_EP_DATA_IN;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (istatus & USB_ISTAT_SLEEP_MASK) {
|
|
/* Enable resume interrupt */
|
|
USB0->INTEN |= USB_INTEN_RESUMEEN_MASK;
|
|
msg.ep = 0U;
|
|
msg.type = USB_DC_CB_TYPE_MGMT;
|
|
msg.cb = USB_DC_SUSPEND;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
}
|
|
|
|
if (istatus & USB_ISTAT_RESUME_MASK) {
|
|
/* Disable resume interrupt */
|
|
USB0->INTEN &= ~USB_INTEN_RESUMEEN_MASK;
|
|
msg.ep = 0U;
|
|
msg.type = USB_DC_CB_TYPE_MGMT;
|
|
msg.cb = USB_DC_RESUME;
|
|
k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT);
|
|
}
|
|
|
|
/* Clear interrupt status bits */
|
|
USB0->ISTAT = istatus;
|
|
}
|
|
|
|
/*
|
|
* This thread is only used to not run the USB device stack and endpoint
|
|
* callbacks in the ISR context, which happens when an callback function
|
|
* is called. TODO: something similar should be implemented in the USB
|
|
* device stack so that it can be used by all drivers.
|
|
*/
|
|
static void usb_kinetis_thread_main(void *arg1, void *unused1, void *unused2)
|
|
{
|
|
ARG_UNUSED(arg1);
|
|
ARG_UNUSED(unused1);
|
|
ARG_UNUSED(unused2);
|
|
struct cb_msg msg;
|
|
uint8_t ep_idx;
|
|
|
|
while (true) {
|
|
k_msgq_get(&usb_dc_msgq, &msg, K_FOREVER);
|
|
ep_idx = USB_EP_GET_IDX(msg.ep);
|
|
|
|
if (msg.type == USB_DC_CB_TYPE_EP) {
|
|
switch (msg.cb) {
|
|
case USB_DC_EP_SETUP:
|
|
if (dev_data.ep_ctrl[ep_idx].cb_out) {
|
|
dev_data.ep_ctrl[ep_idx].cb_out(msg.ep,
|
|
USB_DC_EP_SETUP);
|
|
}
|
|
break;
|
|
case USB_DC_EP_DATA_OUT:
|
|
if (dev_data.ep_ctrl[ep_idx].cb_out) {
|
|
dev_data.ep_ctrl[ep_idx].cb_out(msg.ep,
|
|
USB_DC_EP_DATA_OUT);
|
|
}
|
|
break;
|
|
case USB_DC_EP_DATA_IN:
|
|
if (dev_data.ep_ctrl[ep_idx].cb_in) {
|
|
dev_data.ep_ctrl[ep_idx].cb_in(msg.ep,
|
|
USB_DC_EP_DATA_IN);
|
|
}
|
|
break;
|
|
default:
|
|
LOG_ERR("unknown msg");
|
|
break;
|
|
}
|
|
} else if (dev_data.status_cb) {
|
|
switch (msg.cb) {
|
|
case USB_DC_RESET:
|
|
dev_data.status_cb(USB_DC_RESET, NULL);
|
|
break;
|
|
case USB_DC_ERROR:
|
|
dev_data.status_cb(USB_DC_ERROR, NULL);
|
|
break;
|
|
case USB_DC_SUSPEND:
|
|
dev_data.status_cb(USB_DC_SUSPEND, NULL);
|
|
break;
|
|
case USB_DC_RESUME:
|
|
dev_data.status_cb(USB_DC_RESUME, NULL);
|
|
break;
|
|
default:
|
|
LOG_ERR("unknown msg");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int usb_kinetis_init(void)
|
|
{
|
|
|
|
(void)memset(bdt, 0, sizeof(bdt));
|
|
k_thread_create(&dev_data.thread, dev_data.thread_stack,
|
|
USBD_THREAD_STACK_SIZE,
|
|
usb_kinetis_thread_main, NULL, NULL, NULL,
|
|
K_PRIO_COOP(2), 0, K_NO_WAIT);
|
|
k_thread_name_set(&dev_data.thread, "usb_kinetis");
|
|
|
|
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
|
|
usb_kinetis_isr_handler, 0, 0);
|
|
irq_enable(DT_INST_IRQN(0));
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(usb_kinetis_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|